~ubuntu-branches/ubuntu/saucy/solfege/saucy

« back to all changes in this revision

Viewing changes to mpd/musicalpitch.py

  • Committer: Bazaar Package Importer
  • Author(s): Tom Cato Amundsen
  • Date: 2010-03-28 06:34:28 UTC
  • mfrom: (1.1.10 upstream) (2.1.7 sid)
  • Revision ID: james.westby@ubuntu.com-20100328063428-wg2bqvoce2aq4xfb
Tags: 3.15.9-1
* New upstream release.
* Redo packaging. 

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# GNU Solfege - free ear training software
2
 
# Copyright (C) 2000, 2001, 2002, 2003, 2004, 2007, 2008  Tom Cato Amundsen
3
 
#
4
 
# This program is free software: you can redistribute it and/or modify
5
 
# it under the terms of the GNU General Public License as published by
6
 
# the Free Software Foundation, either version 3 of the License, or
7
 
# (at your option) any later version.
8
 
#
9
 
# This program is distributed in the hope that it will be useful,
10
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 
# GNU General Public License for more details.
13
 
#
14
 
# You should have received a copy of the GNU General Public License
15
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
 
 
17
 
 
18
 
"""
19
 
>>> import locale, gettext
20
 
>>> gettext.NullTranslations().install()
21
 
>>> a = MusicalPitch.new_from_notename("g")
22
 
>>> b = MusicalPitch.new_from_notename("f")
23
 
>>> print a - b
24
 
2
25
 
>>> print (a - 2).get_octave_notename()
26
 
f
27
 
>>> print (a + 3).get_octave_notename()
28
 
ais
29
 
>>> print a < b
30
 
0
31
 
>>> print a > b
32
 
1
33
 
>>> print MusicalPitch.new_from_int(55) == a
34
 
1
35
 
>>> print MusicalPitch.new_from_notename(a.get_notename()) == a
36
 
1
37
 
>>> print a.clone() == a, id(a) == id(a.clone())
38
 
True False
39
 
>>> a=MusicalPitch()
40
 
>>> print a.m_octave_i == a.m_accidental_i == a.m_octave_i == 0
41
 
1
42
 
>>> print a.semitone_pitch()
43
 
48
44
 
>>> print a.get_octave_notename()
45
 
c
46
 
>>> print (a+2).get_octave_notename()
47
 
d
48
 
>>> print (2+a).get_octave_notename()
49
 
d
50
 
>>> print MusicalPitch.new_from_notename("des'").get_notename()
51
 
des
52
 
>>> print MusicalPitch.new_from_int(50).semitone_pitch()
53
 
50
54
 
>>> print MusicalPitch.new_from_int(50).get_notename()
55
 
d
56
 
>>> n=MusicalPitch.new_from_notename("fis,")
57
 
>>> print n.get_user_octave_notename()
58
 
f#,
59
 
>>> n=MusicalPitch.new_from_notename("b,,")
60
 
>>> print n.get_octave_notename()
61
 
b,,
62
 
>>> print n.get_user_octave_notename()
63
 
b,,
64
 
>>> gettext.translation('solfege', './share/locale/', languages=['nb_NO']).install()
65
 
>>> print n.get_octave_notename()
66
 
b,,
67
 
>>> print n.get_user_octave_notename()
68
 
<sub>1</sub>H
69
 
>>> print n.get_user_notename()
70
 
h
71
 
>>> print _("Close")
72
 
Lukk
73
 
>>> n = MusicalPitch()
74
 
>>> n.set_from_notename("d'")
75
 
>>> print n.get_octave_notename()
76
 
d'
77
 
"""
78
 
 
79
 
import logging
80
 
import random
81
 
import _exceptions
82
 
 
83
 
# The following are here so that the strings are caught by pygettext
84
 
_("notename|c")
85
 
_("notename|cb")
86
 
_("notename|cbb")
87
 
_("notename|c#")
88
 
_("notename|cx")
89
 
_("notename|d")
90
 
_("notename|db")
91
 
_("notename|dbb")
92
 
_("notename|d#")
93
 
_("notename|dx")
94
 
_("notename|e")
95
 
_("notename|eb")
96
 
_("notename|ebb")
97
 
_("notename|e#")
98
 
_("notename|ex")
99
 
_("notename|f")
100
 
_("notename|fb")
101
 
_("notename|fbb")
102
 
_("notename|f#")
103
 
_("notename|fx")
104
 
_("notename|g")
105
 
_("notename|gb")
106
 
_("notename|gbb")
107
 
_("notename|g#")
108
 
_("notename|gx")
109
 
_("notename|a")
110
 
_("notename|ab")
111
 
_("notename|abb")
112
 
_("notename|a#")
113
 
_("notename|ax")
114
 
_("notename|b")
115
 
_("notename|bb")
116
 
_("notename|bbb")
117
 
_("notename|b#")
118
 
_("notename|bx")
119
 
 
120
 
class InvalidNotenameException(_exceptions.MpdException):
121
 
    def __init__(self, n):
122
 
        _exceptions.MpdException.__init__(self)
123
 
        self.m_notename = n
124
 
    def __str__(self):
125
 
        return _("Invalid notename: %s") % self.m_notename
126
 
 
127
 
class MusicalPitch:
128
 
    LOWEST_STEPS = -28
129
 
    HIGHEST_STEPS = 47
130
 
    def clone(self):
131
 
        r = MusicalPitch()
132
 
        r.m_octave_i = self.m_octave_i
133
 
        r.m_notename_i = self.m_notename_i
134
 
        r.m_accidental_i = self.m_accidental_i
135
 
        return r
136
 
    def new_from_notename(n):
137
 
        assert isinstance(n, basestring)
138
 
        r = MusicalPitch()
139
 
        r.set_from_notename(n)
140
 
        return r
141
 
    new_from_notename = staticmethod(new_from_notename)
142
 
    def new_from_int(i):
143
 
        assert type(i) == type(0)
144
 
        r = MusicalPitch()
145
 
        r.set_from_int(i)
146
 
        return r
147
 
    new_from_int = staticmethod(new_from_int)
148
 
    def __init__(self):
149
 
        """
150
 
         c,,,, is lowest: m_octave_i == -4, steps() == -28
151
 
         g'''''' is highest: m_octave_i = 6, steps() == 46
152
 
        """
153
 
        self.m_octave_i = self.m_accidental_i = self.m_notename_i = 0
154
 
    def transpose_by_musicalpitch(self, P):
155
 
        """Silly function used by mpd/parser.py and company
156
 
        (d') transposes up one major second.
157
 
        """
158
 
        tra = P.semitone_pitch() - 60
159
 
        old_p = self.semitone_pitch()
160
 
        self.m_notename_i = self.m_notename_i + P.m_notename_i
161
 
        self.m_accidental_i = self.m_accidental_i + P.m_accidental_i
162
 
        if self.m_notename_i > 6:
163
 
            self.m_notename_i = self.m_notename_i - 7
164
 
            self.m_octave_i = self.m_octave_i + 1
165
 
        self.m_octave_i = self.m_octave_i + P.m_octave_i - 1
166
 
        if self.semitone_pitch()-old_p < tra:
167
 
            self.m_accidental_i = self.m_accidental_i + 1
168
 
        elif self.semitone_pitch()-old_p > tra:
169
 
            self.m_accidental_i = self.m_accidental_i - 1
170
 
        self.sanitate_accidental()
171
 
        return self
172
 
    def sanitate_accidental(self):
173
 
        """
174
 
        Make use self.m_accidental_i is some of the values -2, -1, 0, 1, 2
175
 
        It can be out of this range if the musicalpitch has been transposed.
176
 
        This function will change notenames like gisisis, where m_accidental_i
177
 
        is 3 to ais where m_accidental_i is 1
178
 
        """
179
 
        if not -3 < self.m_accidental_i < 3:
180
 
            p = self.semitone_pitch()
181
 
            self.set_from_int(p)
182
 
    def enharmonic_flip(self):#FIXME find proper name.
183
 
        """
184
 
        Change the notename, so that gis becomes aes.
185
 
        What about d, should it be cisis or eeses??
186
 
 
187
 
        his,  c deses
188
 
        cisis d deses
189
 
        disis e fes
190
 
        eis   f geses
191
 
        fisis g aeses
192
 
        gisis a beses
193
 
        aisis b ces'
194
 
 
195
 
        cis des
196
 
        dis es
197
 
        fis ges
198
 
        gis aes
199
 
        ais bes
200
 
        """
201
 
        if self.m_accidental_i == 1 and self.m_notename_i < 6:
202
 
            self.m_accidental_i = -1
203
 
            self.m_notename_i += 1
204
 
    def normalize_double_accidental(self):
205
 
        """
206
 
        Change the tone so that we avoid double accidentals.
207
 
        """
208
 
        if self.m_accidental_i == 2:
209
 
            if self.m_notename_i in (0, 1, 3, 4, 5): # c d f g a
210
 
                self.m_notename_i += 1
211
 
                self.m_accidental_i = 0
212
 
            elif self.m_notename_i == 2: # e
213
 
                self.m_notename_i = 3
214
 
                self.m_accidental_i = 1
215
 
            else:
216
 
                assert self.m_notename_i == 6 # b
217
 
                self.m_notename_i = 0
218
 
                self.m_accidental_i = 1
219
 
                self.m_octave_i += 1
220
 
        elif self.m_accidental_i == -2:
221
 
            if self.m_notename_i in (1, 2, 4, 5, 6): # d e g a b
222
 
                self.m_notename_i -= 1
223
 
                self.m_accidental_i = 0
224
 
            elif self.m_notename_i == 3: # f
225
 
                self.m_notename_i = 2
226
 
                self.m_accidental_i = -1
227
 
            else:
228
 
                assert self.m_notename_i == 0
229
 
                self.m_notename_i = 6
230
 
                self.m_accidental_i = -1
231
 
                self.m_octave_i -= 1
232
 
    def steps(self):
233
 
        return self.m_notename_i + self.m_octave_i * 7
234
 
    def semitone_pitch(self):
235
 
        return [0, 2, 4, 5, 7, 9, 11][self.m_notename_i] + \
236
 
               self.m_accidental_i + self.m_octave_i * 12 + 48
237
 
    def set_from_int(self, midiint):
238
 
        self.m_octave_i = (midiint-48)/12
239
 
        self.m_notename_i = {0:0, 1:0, 2:1, 3:1, 4:2, 5:3, 6:3, 7:4, 8:4,
240
 
                             9:5, 10:5, 11:6}[midiint % 12]
241
 
        self.m_accidental_i = midiint-(self.m_octave_i+4)*12 \
242
 
                              -[0, 2, 4, 5, 7, 9, 11][self.m_notename_i]
243
 
    def set_from_notename(self, notename):
244
 
        if not notename:
245
 
            raise InvalidNotenameException(notename)
246
 
        tmp = notename
247
 
        self.m_accidental_i = self.m_octave_i = 0
248
 
        while notename[-1] in ["'", ","]:
249
 
            if notename[-1] == "'":
250
 
                self.m_octave_i = self.m_octave_i + 1
251
 
            elif notename[-1] == ",":
252
 
                self.m_octave_i = self.m_octave_i - 1
253
 
            notename = notename[:-1]
254
 
        if notename.startswith('es'):
255
 
            notename = 'ees' + notename[2:]
256
 
        if notename.startswith('as'):
257
 
            notename = 'aes' + notename[2:]
258
 
        while notename.endswith('es'):
259
 
            self.m_accidental_i = self.m_accidental_i -1
260
 
            notename = notename[:-2]
261
 
        while notename.endswith('is'):
262
 
            self.m_accidental_i = self.m_accidental_i + 1
263
 
            notename = notename[:-2]
264
 
        try:
265
 
            self.m_notename_i = ['c', 'd', 'e', 'f', 'g', 'a', 'b'].index(notename)
266
 
        except ValueError:
267
 
            raise InvalidNotenameException(tmp)
268
 
    def randomize(self, lowest, highest):
269
 
        """
270
 
        lowest and highest can be an integer, string or a MusicalPitch instance
271
 
        """
272
 
        assert type(lowest) == type(highest)
273
 
        if isinstance(lowest, basestring):
274
 
            lowest = MusicalPitch.new_from_notename(lowest).semitone_pitch()
275
 
        if isinstance(highest, basestring):
276
 
            highest = MusicalPitch.new_from_notename(highest).semitone_pitch()
277
 
        self.set_from_int(random.randint(int(lowest), int(highest)))
278
 
        return self
279
 
    def __radd__(self, a):
280
 
        return self + a
281
 
    def __add__(self, i):
282
 
        """
283
 
        MusicalPitch + integer = MusicalPitch
284
 
        MusicalPitch + Interval = MusicalPitch
285
 
        """
286
 
        if type(i) == type(0):
287
 
            v = self.semitone_pitch()
288
 
            if not 0 <= v + i < 128:
289
 
                raise ValueError
290
 
            return MusicalPitch.new_from_int(v+i)
291
 
        elif i.__class__.__name__ == 'Interval':#isinstance(i, interval.Interval):
292
 
            if not 0 <= self.semitone_pitch() + i.get_intvalue() < 128:
293
 
                raise ValueError
294
 
            r = self.clone()
295
 
            _p = r.semitone_pitch()
296
 
            r.m_notename_i = r.m_notename_i + i.m_interval * i.m_dir
297
 
            r.m_octave_i = r.m_octave_i + r.m_notename_i / 7 + i.m_octave * i.m_dir
298
 
            r.m_notename_i = r.m_notename_i % 7
299
 
            _diff = r.semitone_pitch() - _p
300
 
            r.m_accidental_i = r.m_accidental_i + (i.get_intvalue() - _diff)
301
 
            # to avoid notenames like ciscisciscis :
302
 
            if r.m_accidental_i > 2:
303
 
                #                     c  d  f  g  a
304
 
                if r.m_notename_i in (0, 1, 3, 4, 5):
305
 
                    r.m_accidental_i -= 2
306
 
                else:
307
 
                    assert r.m_notename_i in (2, 6), r.m_notename_i
308
 
                    r.m_accidental_i -= 1
309
 
                r.m_notename_i = r.m_notename_i + 1
310
 
                if r.m_notename_i == 7:
311
 
                    r.m_notename_i = 0
312
 
                    r.m_octave_i = r.m_octave_i + 1
313
 
            if r.m_accidental_i < -2:
314
 
                r.m_accidental_i = r.m_accidental_i + 2
315
 
                r.m_notename_i = r.m_notename_i - 1
316
 
                if r.m_notename_i == -1:
317
 
                    r.m_notename_i = 6
318
 
                    r.m_octave_i = r.m_octave_i - 1
319
 
            if not 0 <= int(self) <= 127:
320
 
                raise ValueError
321
 
            return r
322
 
        else:
323
 
            raise _exceptions.MpdException("Cannot add %s" %type(i))
324
 
    def __sub__(self, i):
325
 
        """
326
 
        MusicalPitch - MusicalPitch = integer
327
 
        MusicalPitch - integer = MusicalPitch
328
 
        """
329
 
        if isinstance(i, MusicalPitch):
330
 
            return self.semitone_pitch() - i.semitone_pitch()
331
 
        assert isinstance(i, int)
332
 
        v = self.semitone_pitch()
333
 
        assert 0 <= v - i < 128
334
 
        return MusicalPitch.new_from_int(v-i)
335
 
    def __int__(self):
336
 
        return self.semitone_pitch()
337
 
    def __cmp__(self, B):
338
 
        if (self is None or self is None):
339
 
            return -1
340
 
        diff = self - B
341
 
        if diff < 0:
342
 
            return -1
343
 
        elif diff > 0:
344
 
            return 1
345
 
        else:
346
 
            return 0
347
 
    def __str__(self):
348
 
        return "(MusicalPitch %s)" % self.get_octave_notename()
349
 
    def get_user_notename(self):
350
 
        # xgettext:no-python-format
351
 
        return self._format_notename(_i("notenameformat|%(notename)s"))
352
 
    def get_user_octave_notename(self):
353
 
        # xgettext:no-python-format
354
 
        return self._format_notename(_i("notenameformat|%(notename)s%(oct)s"))
355
 
    def get_notename(self):
356
 
        return self._format_notename("%(utnotename)s")
357
 
    def get_octave_notename(self):
358
 
        return self._format_notename("%(utnotename)s%(oct)s")
359
 
    def _format_notename(self, format_string):
360
 
        """
361
 
        utnotename : untranslated notename, solfege-internal format.
362
 
        notename  : as the value translated in the po file
363
 
        notename2 : lowercase, but capitalized if below the tone c (as
364
 
                    "c" is defined internally in solfege.
365
 
        suboct :  '' (nothing) for c or higher
366
 
                  <sub>1</sub> for c,
367
 
                  <sub>2</sub> for c,,
368
 
        suboct2:  '' (nothing) for c, and higher
369
 
                  <sub>1</sub> for c,,
370
 
                  <sub>2</sub> for c,,,
371
 
        supoct:   '' (nothing) for tones lower than c'
372
 
                  <sup>1</sup> for c'
373
 
                  <sup>2</sup> for c'' etc.
374
 
        """
375
 
        assert -3 < self.m_accidental_i < 3, self.m_accidental_i
376
 
        utnotename = ['c', 'd', 'e', 'f', 'g', 'a', 'b'][self.m_notename_i]\
377
 
                   + ['eses', 'es', '', 'is', 'isis'][self.m_accidental_i+2]
378
 
        notename = "notename|" \
379
 
                 + ['c', 'd', 'e', 'f', 'g', 'a', 'b'][self.m_notename_i]\
380
 
                 + ['bb', 'b', '', '#', 'x'][self.m_accidental_i+2]
381
 
        notename = _i(notename)
382
 
        if self.m_octave_i < 0:
383
 
            notename2 = notename.capitalize()
384
 
        else:
385
 
            notename2 = notename
386
 
        if self.m_octave_i > 0:
387
 
            oct = "'" * self.m_octave_i
388
 
        elif self.m_octave_i < 0:
389
 
            oct = "," * (-self.m_octave_i)
390
 
        else:
391
 
            oct = ""
392
 
        if self.m_octave_i < 0:
393
 
            suboct = "<sub>%s</sub>" % (-self.m_octave_i)
394
 
        else:
395
 
            suboct = ""
396
 
        if self.m_octave_i < -1:
397
 
            suboct2 = "<sub>%s</sub>" % (-self.m_octave_i-1)
398
 
        else:
399
 
            suboct2 = ""
400
 
        if self.m_octave_i > 0:
401
 
            supoct = "<sup>%s</sup>" % (self.m_octave_i)
402
 
        else:
403
 
            supoct = ""
404
 
        D = {'utnotename': utnotename,
405
 
             'notename': notename,
406
 
             'notename2': notename2,
407
 
             'suboct': suboct,
408
 
             'suboct2': suboct2,
409
 
             'supoct': supoct,
410
 
             'oct': oct}
411
 
        try:
412
 
            return format_string % D
413
 
        except KeyError:
414
 
            logging.error("musicalpitch: Bad translation of notenameformat string")
415
 
            return "%(notename)s%(oct)s" % D
416