# GNU Solfege - free ear training software
# Copyright (C) 2000, 2001, 2002, 2003, 2004, 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/>.
from __future__ import absolute_import
import math
import random
import re
import urlparse
from solfege import cfg
from solfege import mpd
from solfege import soundcard
from solfege.mpd.const import DEFAULT_VELOCITY
def mangle_email(email):
email = email.replace("@", " ,, ")
email = email.replace(".", " ,, ")
return email
def int_to_intervalname(i, shortname=None, updown=None):
if shortname:
n = mpd.interval.short_name[abs(i)]
else:
n = mpd.Interval.new_from_int(abs(i)).get_name()
if updown:
if i > 0:
n = "%s%s" % (n, u"\u2191")
elif i < 0:
n = "%s%s" % (n, u"\u2193")
return n
def filter_intervals_within_range(lowest, highest, irange):
assert isinstance(lowest, basestring)
assert isinstance(highest, basestring)
lowest = mpd.notename_to_int(lowest)
highest = mpd.notename_to_int(highest)
i = highest - lowest
return [x for x in irange if abs(x) <= i]
def random_interval(tonika, lowest, highest, irange):
"""
Return an int representing the interval.
Return None if it is not possible to create an interval.
"""
if isinstance(tonika, mpd.MusicalPitch):
tonika = tonika.semitone_pitch()
assert isinstance(tonika, int)
assert type(lowest) == type(highest)
if isinstance(lowest, basestring):
lowest = mpd.notename_to_int(lowest)
if isinstance(highest, basestring):
highest = mpd.notename_to_int(highest)
assert isinstance(lowest, int)
assert isinstance(highest, int)
assert lowest <= highest
assert isinstance(irange, list)
v = []
for i in irange:
if lowest <= tonika + i <= highest:
v.append(i)
if not v:
return None
return random.choice(v)
def random_tonika_and_interval(lowest, highest, irange):
""" Return a tuple (tonika, interval) of types (MusicalPitch, int).
Return (None, None) if it is not possible to make an interval
because of a conflict between the lowest/highest and the available
intervals
lowest
highest an integer or a string representing a notename
irange list of integers representing intervals. 1 is minor
second up, -2 is major second down
"""
assert type(lowest) == type(highest)
if isinstance(lowest, basestring):
lowest = mpd.notename_to_int(lowest)
if isinstance(highest, basestring):
highest = mpd.notename_to_int(highest)
assert isinstance(lowest, int)
assert isinstance(highest, int)
assert lowest <= highest
assert isinstance(irange, list)
# first we find out what is the largest interval we can have
i = highest - lowest
# then filter irange to only use intervals <= i
v = [x for x in irange if abs(x) <= i]
if not v:
return None, None
interval = random.choice(v)
# then, using that interval, make a list of possible tonikas
tl = []
for t in range(lowest, highest + 1):
if lowest <= t + interval <= highest:
tl.append(t)
tonika = mpd.MusicalPitch.new_from_int(random.choice(tl))
return tonika, interval
def adjust_low_high_to_irange(lowest, highest, irange):
"""
lowest
highest string representing a notename
irange list of integers representing intervals. 1 is minor
second up, -2 is major second down
return tuple with two integers
"""
assert isinstance(lowest, basestring)
assert isinstance(highest, basestring)
L = mpd.notename_to_int(lowest)
H = mpd.notename_to_int(highest)
# find the largest interval that can be asked for
r = max(abs(max(irange)), abs(min(irange)))
# adjust the lowest and highest note, if the intervals are larger than
# the range we should try to stay within
if H - L < r:
H = H + (r - (H - L)) / 2
L = L - (r - (H - L))
return L, H
def un_escape_url_string(s):
r = re.compile("(%([0-9A-F][0-9A-F]))")
m = r.search(s)
def f(m):
return chr(eval("0x%s" % m.groups()[1]))
return r.sub(f, s)
def _str_to_dict(s):
D = {}
if s:
V = s.split(";")
for e in V:
n, v = e.split("=")
D[n.strip()] = v.strip()
for k in D:
D[k] = un_escape_url_string(D[k])
return D
def freq_to_notename_cent(freq):
e = 440.0
if e > freq:
while e > freq:
e = e / 2
else:
while e < freq/2:
e = e * 2
d = freq / e
v = 12 * math.log(d) / math.log(2)
i = int(v)
cent = (v-i) * 100
n = ('a', 'ais', 'b', 'c', 'cis', 'd', 'dis', 'e', 'f', 'fis', 'g', 'gis')
if cent > 50:
return n[(i+1) % 12], cent-100
return n[int(v)], (v-int(v)) * 100
def compare_version_strings(A, B):
"""
Works with version strings like 1, 1.0, 1.1.3, 1.4.3.2
Returns:
-1 if A < B
0 if A == B
1 if A > B
"""
if A == B == "":
return 0
elif A == "":
return -1
elif B == "":
return 1
av = map(lambda s: int(s), A.split("."))
bv = map(lambda s: int(s), B.split("."))
x = 0
while len(av) > x < len(bv):
if av[x] > bv[x]:
return 1
elif av[x] < bv[x]:
return -1
x = x + 1
if len(av) > len(bv):
return 1
elif len(av) < len(bv):
return -1
return 0
class Url:
def __init__(self, href):
self.href = href
self.protocol, loc, path, params, query, self.anchor = urlparse.urlparse(self.href)
if not self.protocol:
self.filename = path
elif self.protocol == 'solfege':
v = path.split("/")
self.action = v[0]
self.lessonfile = "/".join(v[1:])
if len(v) > 2 and self.lessonfile and '?' in self.lessonfile:
self.lessonfile, self.config = self.lessonfile.split('?', 1)
else:
self.config = {}
if self.config:
self.config = _str_to_dict(self.config)
def string_get_line_at(s, idx):
"""
Return the whole line, excluding new-line characters, that
the character at idx is part of. It the char at idx is a new-line
character, then we consider this char the last character of the line.
"""
start = idx
while start > 0 and s[start -1] != "\n":
start -= 1
end = idx
if s[end] != "\n":
while end < len(s) -1 and s[end + 1] != "\n":
end += 1
return s[start:end+1].strip("\n")
def new_track():
t1 = mpd.Track()
t1.set_bpm(cfg.get_int('config/default_bpm'))
t1.set_volume(cfg.get_int('config/preferred_instrument_volume'))
t1.set_patch(cfg.get_int('config/preferred_instrument'))
return t1
def new_percussion_track():
t1 = mpd.PercussionTrack()
t1.set_bpm(cfg.get_int('config/default_bpm'))
t1.set_volume(cfg.get_int('config/preferred_instrument_volume'))
return t1
def new_2_tracks():
if cfg.get_bool('config/override_default_instrument'):
instr_low = cfg.get_int('config/lowest_instrument')
instr_low_volume = cfg.get_int('config/lowest_instrument_volume')
instr_high = cfg.get_int('config/highest_instrument')
instr_high_volume = cfg.get_int('config/highest_instrument_volume')
else:
instr_low = instr_high = cfg.get_int('config/preferred_instrument')
instr_low_volume = instr_high_volume = cfg.get_int('config/preferred_instrument_volume')
t1 = mpd.Track()
t1.set_bpm(cfg.get_int('config/default_bpm'))
t1.set_patch(instr_low)
t1.set_volume(instr_low_volume)
t2 = mpd.Track()
t2.set_patch(instr_high)
t2.set_volume(instr_high_volume)
return t1, t2
def new_3_tracks():
if cfg.get_bool('config/override_default_instrument'):
instr_low = cfg.get_int('config/lowest_instrument')
instr_low_volume = cfg.get_int('config/lowest_instrument_volume')
instr_middle = cfg.get_int('config/middle_instrument')
instr_middle_volume = cfg.get_int('config/middle_instrument_volume')
instr_high = cfg.get_int('config/highest_instrument')
instr_high_volume = cfg.get_int('config/highest_instrument_volume')
else:
instr_low = instr_middle = instr_high = cfg.get_int('config/preferred_instrument')
instr_low_volume = instr_middle_volume = instr_high_volume = cfg.get_int('config/preferred_instrument_volume')
t1 = mpd.Track()
t1.set_bpm(cfg.get_int('config/default_bpm'))
t1.set_patch(instr_low)
t1.set_volume(instr_low_volume)
t2 = mpd.Track()
t2.set_patch(instr_middle)
t2.set_volume(instr_middle_volume)
t3 = mpd.Track()
t3.set_patch(instr_high)
t3.set_volume(instr_high_volume)
return t1, t2, t3
def play_note(notelen, pitch):
t = new_track()
t.note(notelen, pitch, DEFAULT_VELOCITY)
soundcard.synth.play_track(t)
def play_perc(notelen, pitch):
t = new_percussion_track()
t.note(notelen, pitch)
soundcard.synth.play_track(t)
def play_music(music, tempo, patch, volume, start=None, end=None):
if type(tempo) == type(0):
bpm = tempo
nl = 4
else:
bpm, nl = tempo
score = mpd.parser.parse_to_score_object(music)
tracklist = score.get_midi_events(start, end)
tracklist[0].prepend_bpm(bpm, nl)
[track.prepend_patch(patch) for track in tracklist]
[track.prepend_volume(volume) for track in tracklist]
soundcard.synth.play_track(*tracklist)
def play_music3(music, tempo, instrument, start=None, end=None):
"""
Either a tuple (patch, velocity) or a tuple of 6:
(patch1, velocity1, p2, v2, p3, v3)
"""
if len(instrument) == 2:
instrument = instrument * 3
tracklist = mpd.music_to_tracklist(music, start, end)
tracklist[0].prepend_patch(instrument[4])
tracklist[0].prepend_volume(instrument[5])
a, b = tempo
# FIXME the bpm api is wrong. We set the tempo to one track, and it works for all.
tracklist[0].prepend_bpm(a, b)
for track in tracklist[:-1][1:]:
track.prepend_patch(instrument[2])
track.prepend_volume(instrument[3])
tracklist[-1].prepend_patch(instrument[0])
tracklist[-1].prepend_volume(instrument[1])
soundcard.synth.play_track(*tracklist)
|