Scores - davidpanderson/Numula GitHub Wiki

The nscore module lets you create data structures representing musical scores.

from numula.nscore import *

class Note

Represents a note, with a specified start time, duration, pitch, and volume.

Note(
    time: float,
    dur: float,
    pitch: int,
    vol: float,
    tags: list[str]=[]
)

Creates a Note object. Parameters:

  • time: The note's start time in "score units" in which 1 is the duration of a 4/4 measure; i.e. a 'beat' is 0.25 score units. The default tempo is 60 beats per minute.
  • dur: The note's duration (i.e. the amount of time it sounds) in score units.
  • pitch: The note's pitch in the MIDI scale (0..127, 60 = middle C).
  • vol: The note's volume, 0..1
  • tags: An optional list of text "tags"; these and other note attributes can be used used by nuance functions to select groups of notes.

If you're entering a score, you typically don't create Note objects directly. Instead, use a shorthand notation that creates them for you.

class Score

Represents a set of notes, pedal applications, and measures.

Score(tempo: float=60)

Creates a Score. tempo is in beats per minute. To vary the tempo within the piece, use the timing control functions.

Score.insert_note(note: Note)

Add the given note to the score.

Score.write_midi(filename: str)

Generate a MIDI file for the score.

We can now create some music:

import random

ns = Score()
for i in range(200):
    time = random.uniform(0, 10)
    dur = random.uniform(.1, 1)
    pitch = random.randrange(40, 80)
    vol = random.uniform(.1, .8)
    ns.insert_note(Note(time, dur, pitch, vol))
ns.write_midi("random.midi")

This plays 200 notes randomly spaced over 40 seconds (10 4-beat measures at quarter=60), with random duration, pitch, and volume.

Listen

Other members of Score:

Score.append_note(note: Note)

A Score has a "current time", initially zero. append_note() adds a note starting at that time. It doesn't change the current time.

Score.advance_time(dt: float)

Add dt to the current time. dt may be positive or negative.

Score.insert_score(score: Score, t: float=0, tag: str='')

Merge another Score into this one, starting at time t. The other score's notes, pedals, and measure boundaries are added to this Score, with t added to their start times. If tag is specified, tag the added notes.

Score.append_score(score: Score, tag='')

Append the given Score to this one. If tag is specified, tag the added notes.

Score.append_scores(scores: list[Score], tag='')

Insert the given scores into this Score, starting at the current time. Then advance the current time by the duration of the longest Score.

Score.tag(tag: str)

Add the given tag to all notes in the score.

Score.trim(t0: float, t1: float)

Trim the score to the time range [t0...t1].

print(ns)

prints the score's notes,pedals, and measure boundaries in human-readable form.

class Measure

Represents a measure (i.e. a metric unit).

Measure(t, dur: float, type: str)
  • t: start time of the measure
  • dur: duration of the measure
  • type: a string of your choosing describing the metric structure of the measure.

For example:

Measure(0., '2+2+3/8')

could represent a measure of 2+2+3 eighth notes, starting at time 0.

Score.insert_measure(m: Measure)

Add a Measure object to a Score.

Score.append_measure(duration: float, type: str)

Append a measure of the given duration and type to a Score.

Measures are optional. They provide a framework for nuance based on position in the measure. For example, if you've added Measures to a score you can do things like:

Score.vol_adjust(0.9, lambda n: n.measure_type=='4/4' and n.measure_offset in [1, 2, 3])
Score.vol_adjust(0.8, lambda n: n.measure_type=='4/4' and n.measure_offset not in [0, 1, 2, 3])

attenuates the volume of notes in 4/4 measures based on their position:

  • The downbeat is not attenuated
  • The 2nd, 3rd, and 4th beats are attenuated by .9
  • Other notes are attenuated by .8