Source code for birdears.resolution

from . import MAX_SEMITONES_RESOLVE_BELOW

from .interval import Interval

from .scale import DiatonicScale

from .sequence import Sequence

from .note_and_pitch import get_pitch_by_number
from .note_and_pitch import Chord

from functools import wraps

RESOLUTION_METHODS = {}
# http://stackoverflow.com/questions/5910703/howto-get-all-methods-of-a-pyt\
# hon-class-with-given-decorator
# http://stackoverflow.com/questions/5707589/calling-functions-by-array-ind\
# ex-in-python/5707605#5707605


[docs]def register_resolution_method(f, *args, **kwargs): """Decorator for resolution method functions. Functions decorated with this decorator will be registered in the `RESOLUTION_METHODS` global dict. """ @wraps(f) def decorator(*args, **kwargs): return f(*args, **kwargs) RESOLUTION_METHODS.update({f.__name__: f}) return decorator
[docs]class Resolution: """This class implements methods for different types of question resolutions. A resolution is an answer to a question. It aims to create a mnemonic on how the inverval resolves to the tonic. """ def __init__(self, method, question): """Inits the resolution class. Args: method (str): The method used in the resolution. question (obj): Question object from which to generate the resolution sequence. """ self.METHOD = RESOLUTION_METHODS[method] self.question = question def __call__(self, *args, **kwargs): """Calls the resolution method and pass arguments to it. Returns a `birdears.Sequence` object with the resolution generated by the.method. """ return self.METHOD(question=self.question, *args, **kwargs)
[docs]@register_resolution_method def nearest_tonic(question): """Resolution method that resolve the intervals to their nearest tonics. Args: question (obj): Question object from which to generate the resolution sequence. (this is provided by the `Prequestion` class when it is `__call__`ed) """ tonic_pitch = question.tonic_pitch # if hasattr(question, 'random_pitch'): # random_pitch = tuple(question.random_pitch) # else: # random_pitch = tuple(question.random_pitches) # # TODO: if hasattr(question, 'random_pitches'): raise Exception('NEAREST_TONIC FOR MULTIPLE PITCHES IS STILL TO BE' 'IMPLEMENTED') random_pitch = question.random_pitch duration = question.durations['resol']['duration'] delay = question.durations['resol']['delay'] pos_delay = question.durations['resol']['pos_delay'] # this function will receive: tonic, scale and random_pitch (which may be # chromatic, ie., not in `scale`) semitones = (int(random_pitch) - int(tonic_pitch)) % 12 scale_random_pitch = question.diatonic_scale direction = -1 if (semitones <= MAX_SEMITONES_RESOLVE_BELOW) else +1 # invert `direction` signal if descending: pitch_direction = (direction * -1) if question.is_descending else direction resolution = [] if random_pitch not in scale_random_pitch: # random_pitch is chromatic resolution.append(random_pitch) # if this note is chromatic then the # next ones above or below are in the diatonic for sure, so we # add one semitone and we will have the next diatonic degree: nearest_diatonic_pitch = \ get_pitch_by_number(int(random_pitch) + direction) else: nearest_diatonic_pitch = random_pitch # random_pitch is diatonic resolve_mask = 0 if semitones <= MAX_SEMITONES_RESOLVE_BELOW else 12 nearest_tonic_pitch = get_pitch_by_number(int(random_pitch) + (resolve_mask-semitones)) nearest_tonic_index = scale_random_pitch.index(nearest_tonic_pitch) nearest_diatonic_pitch_index = \ scale_random_pitch.index(nearest_diatonic_pitch) random_pitch_index = nearest_diatonic_pitch_index ohslice = slice(min(nearest_tonic_index, random_pitch_index), max(nearest_tonic_index, nearest_diatonic_pitch_index)+1) resolution = scale_random_pitch[ohslice][::pitch_direction] # is it chromatic? if random_pitch not in scale_random_pitch: resolution.insert(0, random_pitch) if len(resolution) == 1: resolution.append(tonic_pitch) if question.is_harmonic: resolution_harmonic = [] for item in resolution: resolution_harmonic.append(Chord([tonic_pitch, item])) resolution = resolution_harmonic # resolution return Sequence(elements=resolution, duration=duration, delay=delay, pos_delay=pos_delay)
[docs]@register_resolution_method # FIXME : it should both play preq and question def repeat_only(question): """Resolution method that only repeats the sequence elements with given durations. Args: question (obj): Question object from which to generate the resolution sequence. (this is provided by the `Prequestion` class when it is `__call__`ed) """ elements = list() if hasattr(question, 'random_pitches'): elements.extend(question.random_pitches) else: elements.append(question.random_pitch) elements.append(question.tonic_pitch) duration = question.durations['resol']['duration'] delay = question.durations['resol']['delay'] pos_delay = question.durations['resol']['pos_delay'] sequence_list = Sequence(elements, duration=duration, delay=delay, pos_delay=pos_delay) return sequence_list