Secure your code as it's written. Use Snyk Code to scan source code in minutes - no build needed - and fix issues immediately.
elif character == "{":
curly_count += 1
elif character == ")":
paren_count -= 1
elif character == "]":
square_count -= 1
elif character == "}":
curly_count -= 1
elif character == "," and paren_count == square_count == curly_count == 0:
out.append(s[last_split:i-1].strip())
last_split = i
out.append(s[last_split:].strip())
return out
class NotePropertiesDictionary(UserDict, SavesToJSON):
def __init__(self, **kwargs):
NotePropertiesDictionary._standardize_plural_entry("articulations", kwargs)
NotePropertiesDictionary._standardize_plural_entry("noteheads", kwargs)
if len(kwargs["noteheads"]) == 0:
kwargs["noteheads"] = ["normal"]
NotePropertiesDictionary._standardize_plural_entry("notations", kwargs)
NotePropertiesDictionary._standardize_plural_entry("texts", kwargs)
NotePropertiesDictionary._standardize_plural_entry("playback_adjustments", kwargs)
for i, adjustment in enumerate(kwargs["playback_adjustments"]):
if isinstance(adjustment, str):
kwargs["playback_adjustments"][i] = NotePlaybackAdjustment.from_string(adjustment)
if "spelling_policy" not in kwargs:
kwargs["spelling_policy"] = None
# - if "split", the note is split rhythmically at the control points
# - if "none", control points are ignored
"control_point_policy": "split",
# if true, we consider all control points in the engraving process. If false, we only consider local extrema.
"consider_non_extrema_control_points": False,
# if true, the final pitch reached is expressed as a gliss up to a headless grace note
"include_end_grace_note": True,
# this threshold helps determine which gliss control points are worth expressing in notation
# the further a control point is from its neighbors, and the further it deviates from
# the linearly interpolated pitch at that point, the higher its relevance score.
"inner_grace_relevance_threshold": 4.0,
"max_inner_graces_music_xml": 1
}
class GlissandiEngravingSettings(SavesToJSON):
def __init__(self, **settings):
self._control_point_policy = _glissandi_engraving_factory_defaults["control_point_policy"] \
if "control_point_policy" not in settings else settings["control_point_policy"]
try:
assert self.control_point_policy in ("grace", "split", "none")
except AssertionError:
logging.warning("Control point policy must be one of: \"grace\", \"split\", or \"none\". Defaulting "
"to \"{}\".".format(_glissandi_engraving_factory_defaults["control_point_policy"]))
self.control_point_policy = _glissandi_engraving_factory_defaults["control_point_policy"]
self.consider_non_extrema_control_points = \
_glissandi_engraving_factory_defaults["consider_non_extrema_control_points"] \
if "consider_non_extrema_control_points" not in settings \
else settings["consider_non_extrema_control_points"]
self.include_end_grace_note = _glissandi_engraving_factory_defaults["include_end_grace_note"] \
if "include_end_grace_note" not in settings else settings["include_end_grace_note"]
from .transcriber import Transcriber
from ._midi import get_available_midi_input_devices, get_port_number_of_midi_device, \
print_available_midi_input_devices, print_available_midi_output_devices, start_midi_listener
from .instruments import Ensemble, ScampInstrument
from clockblocks import Clock, current_clock
from .utilities import SavesToJSON
from ._dependencies import pynput, pythonosc
from threading import Thread, current_thread
from .spelling import SpellingPolicy
from typing import Union, Tuple, Iterator, Callable, Sequence
from .performance import Performance
import threading
class Session(Clock, Ensemble, Transcriber, SavesToJSON):
"""
A Session combines the functionality of a master Clock, an Ensemble, and a Transcriber.
Since it's a master Clock, it manages global tempo; since it's an Ensemble, you use it to create and keep
track of instruments, and since it's a Transcriber, it allows you to transcribe and generate notation from what
the instruments are playing. You can also use a Session to set up interactive callbacks for MIDI, OSC, mouse
events, and keyboard events.
:param tempo: the initial tempo of the master clock
:param default_soundfont: the default soundfont used by instruments in this session. (Can be overridden at
instrument creation.)
:param default_audio_driver: the default driver used by (soundfont) instruments to output audio. (Can be
overridden at instrument creation.)
:param default_midi_output_device: the default midi_output_device (by name or port number) for outgoing midi
streams. (Again, can be overridden at instrument creation.)
"""
# for instance, if param_value is a Envelope, multiply is zero and add is a Envelope,
# you would end up trying to add two Envelopes, which we don't allow
return self.add if self.multiply == 0 else param_value * self.multiply + self.add
def _to_dict(self):
return self.__dict__
@classmethod
def _from_dict(cls, json_dict):
return cls(**json_dict)
def __repr__(self):
return "ParamPlaybackAdjustment({}, {})".format(self.multiply, self.add)
class NotePlaybackAdjustment(SavesToJSON):
"""
Represents an adjustment to the pitch, volume and/or length of the playback of a single note
:param pitch_adjustment: The desired adjustment for the note's pitch. (None indicates no adjustment)
:param volume_adjustment: The desired adjustment for the note's volume. (None indicates no adjustment)
:param length_adjustment: The desired adjustment for the note's length. (None indicates no adjustment)
:ivar pitch_adjustment: The desired adjustment for the note's pitch. (None indicates no adjustment)
:ivar volume_adjustment: The desired adjustment for the note's volume. (None indicates no adjustment)
:ivar length_adjustment: The desired adjustment for the note's length. (None indicates no adjustment)
"""
def __init__(self, pitch_adjustment: ParamPlaybackAdjustment = None,
volume_adjustment: ParamPlaybackAdjustment = None,
length_adjustment: ParamPlaybackAdjustment = None):
self.pitch_adjustment: ParamPlaybackAdjustment = pitch_adjustment
@classmethod
def _from_dict(cls, json_dict):
ensemble = cls(**json_dict)
for instrument in ensemble.instruments:
instrument.set_ensemble(ensemble)
return ensemble
def __str__(self):
return "Ensemble({})".format(self.instruments)
def __repr__(self):
return "Ensemble._from_dict({})".format(self._to_dict())
class ScampInstrument(SavesToJSON):
"""
Instrument class that does the playing of the notes. Generally this will be created through one of the
"new_part" methods of the Session or Ensemble class.
:param name: name of this instrument (e.g. when printed in a score)
:param ensemble: Ensemble to which this instrument will belong.
:param default_spelling_policy: sets :attr:`ScampInstrument.default_spelling_policy`
:param clef_preference: sets :attr:`ScampInstrument.clef_preference`
:ivar name: name of this instrument (e.g. when printed in a score)
:ivar name_count: when there are multiple instruments of the same name within an Ensemble, this variable assigns
each a unique index (starting with 0), to distinguish them
:ivar ensemble: Ensemble to which this instrument will belong.
:ivar playback_implementations: list of PlaybackImplementation(s) used to actually playback notes
"""
"end_note": "end_note",
"change_pitch": "change_pitch",
"change_volume": "change_volume",
"change_quality": "change_quality"
},
"adjustments": PlaybackDictionary(articulations={
"staccato": NotePlaybackAdjustment.scale_params(length_scale=0.5),
"staccatissimo": NotePlaybackAdjustment.scale_params(length_scale=0.3),
"tenuto": NotePlaybackAdjustment.scale_params(length_scale=1.2),
"accent": NotePlaybackAdjustment.scale_params(volume_scale=1.2),
"marcato": NotePlaybackAdjustment.scale_params(volume_scale=1.5),
})
}
class PlaybackSettings(SavesToJSON):
def __init__(self, **settings):
self.named_soundfonts = _playback_settings_factory_defaults["named_soundfonts"] \
if "named_soundfonts" not in settings else settings["named_soundfonts"]
self.default_audio_driver = _playback_settings_factory_defaults["default_audio_driver"] \
if "default_audio_driver" not in settings else settings["default_audio_driver"]
self.default_midi_output_device = _playback_settings_factory_defaults["default_midi_output_device"] \
if "default_midi_output_device" not in settings else settings["default_midi_output_device"]
self.default_max_midi_pitch_bend = _playback_settings_factory_defaults["default_max_midi_pitch_bend"] \
if "default_max_midi_pitch_bend" not in settings else settings["default_max_midi_pitch_bend"]
self.osc_message_addresses = _playback_settings_factory_defaults["osc_message_addresses"] \
if "osc_message_addresses" not in settings else settings["osc_message_addresses"]
self.adjustments = _playback_settings_factory_defaults["adjustments"] \
if "adjustments" not in settings else settings["adjustments"]
def restore_factory_defaults(self):
from .utilities import resolve_relative_path, SavesToJSON, get_average_square_correlation
from .settings import playback_settings
from ._dependencies import fluidsynth, Sf2File
import logging
from collections import OrderedDict
import re
import os.path
class SoundfontHost(SavesToJSON):
def __init__(self, soundfonts=(), audio_driver="default"):
"""
A SoundfontHost hosts an instance of fluidsynth with one or several soundfonts loaded.
It can be called upon to add or remove instruments from that synth
:param soundfonts: one or several soundfonts to be loaded
:param audio_driver: the audio driver to use
"""
if isinstance(soundfonts, str):
soundfonts = (soundfonts, )
if fluidsynth is None:
raise ModuleNotFoundError("FluidSynth not available.")
self.audio_driver = playback_settings.default_audio_driver if audio_driver == "default" else audio_driver
elif character == "{":
curly_count += 1
elif character == ")":
paren_count -= 1
elif character == "]":
square_count -= 1
elif character == "}":
curly_count -= 1
elif character == " " and paren_count == square_count == curly_count == 0:
out.append(s[last_split:i-1].strip())
last_split = i
out.append(s[last_split:].strip())
return out
class ParamPlaybackAdjustment(SavesToJSON):
"""
Represents a multiply/add playback adjustment to a single parameter.
(The multiply happens first, then the add.
:param multiply: how much to multiply by
:param add: how much to add
:ivar multiply: how much to multiply by
:ivar add: how much to add
"""
def __init__(self, multiply: float = 1, add: float = 0):
self.multiply = multiply
self.add = add
@classmethod
from ._dependencies import pythonosc
from typing import Tuple, Optional
import logging
from .settings import playback_settings
from .utilities import SavesToJSON, SavesToJSONMeta
class _PlaybackImplementationMeta(SavesToJSONMeta):
def __call__(cls, *args, **kwargs):
instance = super().__call__(*args, **kwargs)
instance._try_to_bind_host_instrument()
return instance
class PlaybackImplementation(SavesToJSON, metaclass=_PlaybackImplementationMeta):
"""
Abstract base class for playback implementations, which do the actual work of playback, either by playing sounds or
by sending messages to external synthesizers to play sounds.
:param host_instrument: The :class:`~scamp.instruments.ScampInstrument` that will use this playback implementation
for playback. When this PlaybackImplementation is constructed, it is automatically added to the list of
PlaybackImplementations that the host instrument uses.
"""
def __init__(self, host_instrument: 'instruments_module.ScampInstrument'):
# this is a fallback: if the instrument does not belong to an ensemble, it does not have
# shared resources and so it will rely on its own privately held resources.
self._resources = None
self._host_instrument = host_instrument
# This are set when the host instrument is set
import itertools
from copy import deepcopy
from functools import total_ordering
from expenvelope import Envelope
from .utilities import SavesToJSON
from ._note_properties import NotePropertiesDictionary
from .settings import engraving_settings
@total_ordering
class PerformanceNote(SavesToJSON):
def __init__(self, start_time, length, pitch, volume, properties):
"""
Represents a single note played by a ScampInstrument
:param start_time: the start beat of the
:type start_time: float
:param length: the length of the note in beats (either a float or a list of floats representing tied segments)
:param pitch: the pitch of the note (float or Envelope)
:param volume: the volume of the note (float or Envelope)
:param properties: dictionary of note properties, or string representing those properties
"""
self.start_time = start_time
# if length is a tuple, this indicates that the note is to be split into tied segments
self.length = length
# if pitch is a tuple, this indicates a chord