Source code for strauss.score

""" The :obj:`score` submodule: musical constraints on what :obj:`Sources` can play

This submodule is intended to control any musical aspects of the
sonification, by providing constraints on harmonic and rhythmic
aspects. 

Todo:
    * Incorporate more rhythmic options, such as quantisation
    * Have the :obj:`Score` class handle pitch and rhythm assignment
      from :obj:`Sources` directly, instead of the :obj:`Sonification`
      class. 
    * Support chord change intervals as a mapped parameter
    * Provide template chord charts for users 
"""

from . import stream
from . import notes
import numpy as np
import pychord as chrd
import numbers
import re

[docs] class Score: """ Class defining the musical score for the sonification The Score class controls the musical aspects of the sonification. Currently supports a `chordal` score, defining a set of notes that can be played for each section of the simulation. The score also controls the length of the sonification. Note: Currently, no rhythmic constraints are incorporated in the score. Chords are divided evenly over the length of the sonification. For example for a one minute sonification (:obj:`length = '1m 0s'`) the :obj:`chord_sequence = "Am7_3 | D9_3 | Gmaj7_2"` plays each chord for 20s each. Chaining the same chord can be used to change intervals, (e.g. :obj:`chord_sequence = "F_3 | F_3 | C_4"` plays F for 40s and C for 20s.) Args: chord_sequence: (:obj:`str` or :obj:`list`): The chord or chord sequence used for the sonification. If a string, parse using :obj:`parse_chord_sequence`. If a :obj:`list`, each entry is a :obj:`list` of strings or floats, representing the notes of a chord. notes are represented as strings using scientific notation, e.g. :obj:`[['C3','E3', 'G3'], ['C3', 'F3', 'A4']]`. If floats, take values as note frequency in Hz. NOTE: currently only supported in compination with the :obj:`Synthesiser` generator class. length: (:obj:`str` or :obj:`float`): the length of the sonification. If a string, parse minutes and seconds from format 'Xm Y.Zs'. If a float read as seconds. """ def __init__(self, chord_sequence, length): # check types to handle score length correctly if isinstance(length, str): regex = "([0-9]*)m\s*([0-9]*.[0-9]*)s" reobj = re.match(regex, length, re.M | re.I) self.length = float(reobj.group(1))*60. + float(reobj.group(2)) else: self.length = length # check types to handle different chord formats correctly if isinstance(chord_sequence, list): self.note_sequence = chord_sequence if isinstance(chord_sequence, str): self.note_sequence = parse_chord_sequence(chord_sequence) # number of chords in the sequence self.nchords = len(self.note_sequence) self.nintervals = [len(c) for c in self.note_sequence] # For now, chords changes are just equally spaced in the timeline self.fracbins = np.linspace(0,1, self.nchords+1) self.timebins = self.length * self.fracbins
[docs] def parse_chord_sequence(chord_sequence): """ parse a chord sequence from a string Args: chord_sequence (:obj:`str`): chord sequence to parse, with chord names appended with '_N' where N is the root octave of the chord, and each chord is separated by a pipe character, '|'. Returns: note_list (:obj:`list(list)`): the chord sequence represented as a list of lists, where each sub-list is a chord comprised of strings representing each note in scentific notation (e.g. 'A4') """ chord_list = chord_sequence.split("|") note_list = [] note_frqs = [] for i in range(len(chord_list)): chord_list[i] = chord_list[i].strip() chord_list[i] = chord_list[i].split('_') chord_notes = notes.chord_notes(*chord_list[i]) note_list.append(chord_notes) return note_list
if __name__ == '__main__': chords = "Am7_4 | G_3 | F_3 | E7b9_3 " # chords = "Am7_4" Score(chords)