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)