[go: up one dir, main page]

File: interval.py

package info (click to toggle)
solfege 3.10.3-1
  • links: PTS
  • area: main
  • in suites: lenny
  • size: 12,408 kB
  • ctags: 4,270
  • sloc: python: 22,161; xml: 7,536; ansic: 4,442; makefile: 685; sh: 308
file content (316 lines) | stat: -rw-r--r-- 10,740 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
# GNU Solfege - free ear training software
# Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008  Tom Cato Amundsen
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

"""
The Interval class is a class that is used to do math with
intervals and musical pitches. You use this class if you
need to know the difference between a augmented second and
a minor third. If you don't require this you can probably
ise the simpler function mpdutils.int_to_intervalname
"""

import re
import _exceptions

# We have to list all possible interval names here so that xgettext
# can find them and add them to the .pot file for translation. We
# cannot translate the interval quality and interval number separately
# because we don't know how to concat them on other languages.
if 0:
    _("perfect unison")
    _("diminished unison")
    _("augmented unison")
    #
    _("diminished second")
    _("minor second")
    _("major second")
    _("augmented second")
    #
    _("diminished third")
    _("minor third")
    _("major third")
    _("augmented third")
    #
    _("diminished fourth")
    _("perfect fourth")
    _("augmented fourth")
    #
    _("diminished fifth")
    _("perfect fifth")
    _("augmented fifth")
    #
    _("diminished sixth")
    _("minor sixth")
    _("major sixth")
    _("augmented sixth")
    #
    _("diminished seventh")
    _("minor seventh")
    _("major seventh")
    _("augmented seventh")
    #
    _("diminished octave")
    _("perfect octave")
    _("augmented octave")
    #
    _("minor ninth")
    _("major ninth")
    _("minor decim")
    _("major decim")
    _("interval|unison")
    _("interval|second")
    _("interval|third")
    _("interval|fourth")
    _("interval|fifth")
    _("interval|sixth")
    _("interval|seventh")
    _("interval|octave")
    _("interval|ninth")
    _("interval|decim")
    # translators: Only translate the word after interval| , and don't include
    # interval| in the translated string. So for Norwegians, translate
    # "interval|diminished" to "forminsket". Do similar for all strings
    # that are preceded with "interval|"
    _("interval|diminished")
    _("interval|perfect")
    _("interval|augmented")
    _("interval|minor")
    _("interval|major")
    _("interval|doubly-diminished")
    _("interval|doubly-augmented")

short_name = (_i("interval|u"),
              _i("interval|m2"), _i("interval|M2"),
              _i("interval|m3"), _i("interval|M3"),
              _i("interval|4"), _i("interval|d5"),
              _i("interval|5"),
              _i("interval|m6"), _i("interval|M6"),
              _i("interval|m7"), _i("interval|M7"),
              _i("interval|8"),
              _i("interval|m9"), _i("interval|M9"),
              _i("interval|m10"), _i("interval|M10"))

class InvalidIntervalnameException(_exceptions.MpdException):
    def __init__(self, notename):
        _exceptions.MpdException.__init__(self)
        self.m_intervalname = notename
    def __str__(self):
        return _("Invalid interval name: %s") % self.m_intervalname


class Interval:
    """
    The interval is internally a interval less than octave
    pluss n octaves. The data variables:
      m_dir
      m_octave
      m_interval
      m_mod
    should NOT be touched by anyone, except MusicalPitch.__add__
    """
    _nn_to_interval_quality = {
        'p': 'perfect',
        'dd': 'doubly-diminished',
        'd': 'diminished',
        'm': 'minor',
        'M': 'major',
        'a': 'augmented',
        'aa': 'doubly-augmented',
        }
    def __init__(self, iname=None):
        self.m_dir = 1 # value as to be 1 or -1 for initialised obj
        self.m_octave = 0 # 0, 1, 2, 3 etc
        self.m_interval = 0 # 0:unison, 1:seond, ... 6: septim
        # unison:              dim   perfect   aug
        # second:  -2:dim -1:minor 0:major   1:aug
        # third:      dim    minor   major     aug
        # fourth:              dim   perfect   aug
        # fifth:               dim   perfect   aug
        # sixth:      dim    minor   major     aug
        # seventh:    dim    minor   major     aug
        self.m_mod = 0
        if iname:
            self.set_from_string(iname)
    def nn_to_translated_quality(interval_quality):
        """
        Return translated interval quality from internal short string.
        interval_quality can be: dd, d, m, M, a, a, p
        The C locale will return english names, as 'perfect' and 'diminished'
        """
        # Hack, just to xgettext should not grab the string for translation
        xgettext_wont_find_us = _i
        return xgettext_wont_find_us("interval|%s" % Interval._nn_to_interval_quality[interval_quality])
    nn_to_translated_quality = staticmethod(nn_to_translated_quality)
    def number_name(steps):
        try:
            return {
                1: "unison",
                2: "second",
                3: "third",
                4: "fourth",
                5: "fifth",
                6: "sixth",
                7: "seventh",
                8: "octave",
                9: "ninth",
                10: "decim"}[steps]
        except KeyError:
            return "%ith" % steps
    number_name = staticmethod(number_name)
    def errorcheck(self):
        assert 0 <= self.m_interval <= 6
        assert -2 <= self.m_mod <= 1 # should be increased to -3 <= x <= 2
        assert self.m_octave >= 0
        assert self.m_dir in (-1, 1)
    def _set(self, direction, interval, mod, octave):
        self.m_dir = direction
        self.m_interval = interval
        self.m_mod = mod
        self.m_octave = octave
        if __debug__:
            self.errorcheck()
        return self
    def new_from_int(i):
        assert isinstance(i, int)
        new_int = Interval()
        new_int.set_from_int(i)
        return new_int
    new_from_int = staticmethod(new_from_int)
    def set_from_int(self, i):
        """It returns self to allow chaining: set_from_int(4).pretty_name()
        """
        if i < 0:
            self.m_dir = -1
        else:
            self.m_dir = 1
        self.m_octave = abs(i) / 12
        self.m_mod, self.m_interval = (
               (0, 0),          # unison
               (-1, 1), (0, 1), # second
               (-1, 2), (0, 2), # third
               (0, 3),          # fourth
               (-1, 4),         # dim 5, tritone
               (0, 4),          # fifth
               (-1, 5), (0, 5), # sixth
               (-1, 6), (0, 6))[abs(i) % 12] # seventh
        return self
    def set_from_string(self, s):
        """
        unison  p1
        second  m2 M2
        third   m3 M3
        fourth  p4
        fifth   d5 5
        sixth   m6 M6
        seventh m7 M7
        octave  p8
        none    m9 M9
        decim   m10 M10
        """
        # up or down
        s_orig = s[:]
        s = s.strip()
        if s[0] == "-":
            self.m_dir = -1
            s = s[1:]
        else:
            self.m_dir = 1
        m = re.match("(m|M|d|a|p)(\d+)", s)
        if not m:
            raise InvalidIntervalnameException(s_orig)
        modifier, i = m.groups()
        i = int(i)
        if i <= 7:
            self.m_octave = 0
        else:
            self.m_octave = (i - 1) // 7
        self.m_interval = i - 1 - self.m_octave * 7
        if self.m_interval in (1, 2, 5, 6):
            try:
                self.m_mod = {'d': -2, 'm': -1, 'M': 0, 'a': 1}[modifier]
            except:
                raise InvalidIntervalnameException(s_orig)
        elif self.m_interval in (0, 3, 4):
            try:
                self.m_mod = {'d': -1, 'p': 0, '': 0, 'a': 1}[modifier]
            except:
                raise InvalidIntervalnameException(s_orig)
    def get_intvalue(self):
        if __debug__:
            self.errorcheck()
        return ([0, 2, 4, 5, 7, 9, 11][self.m_interval] + self.m_octave * 12 + self.m_mod) * self.m_dir
    def __str__(self):
        if __debug__:
            self.errorcheck()
        ret = "(Interval %i %imod %io" % (self.m_interval, self.m_mod, self.m_octave)
        if self.m_dir == -1:
            ret = ret + " down)"
        else:
            ret = ret + " up)"
        return ret
    def __repr__(self):
        if self.m_interval in (0, 3, 4):
            return "%s%s" % ({-2: 'dd', -1: 'd', 0: 'p', 1: 'a', 2: 'aa'}[self.m_mod], self.m_interval + 1 + self.m_octave * 7)
        return "%s%s" % ({-2: 'd', -1: 'm', 0: 'M', 1: 'a'}[self.m_mod],  (self.m_interval + 1 + self.m_octave * 7))
    def __eq__(self, interval):
        return self.m_dir == interval.m_dir \
            and self.m_octave == interval.m_octave \
            and self.m_mod == interval.m_mod \
            and self.m_interval == interval.m_interval
    def get_number_name(self):
        """
        Return the translated general name of the interval, like second, third
        etc. (major, minor etc.)
        """
        return _(self.number_name(self.steps()))
    def get_quality_short(self):
        """
        Return a short string telling the quality.
        This is a non-translated short string mostly used
        internally in the program.
        """
        if self.m_interval in (0, 3, 4):
            return {-2: "dd",
                    -1: "d",
                     0: "p",
                     1: "a",
                     2: "aa"}[self.m_mod]
        else:
            assert self.m_interval in (1, 2, 5, 6)
            return {-2: "d",
                    -1: "m",
                     0: "M",
                     1: "a"}[self.m_mod]
    def get_quality(self):
        """
        Return the non-translated interval quality.
        """
        return self._nn_to_interval_quality[self.get_quality_short()]
    def get_cname(self):
        """
        Return the full untranslated interval name, both the number and quality.
        """
        return "%s %s" % (self.get_quality(), self.number_name(self.steps()))
    def get_name(self):
        """
        Return the full translated intervalname, both the number and quality.
        """
        return _(self.get_cname())
    def steps(self):
        return self.m_octave * 7 + self.m_interval + 1