####################################################################################################
#
# Musica - A Music Theory Package for Python
# Copyright (C) 2017 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
####################################################################################################
"""
"""
####################################################################################################
__all__ = [
'InstrumentDatabase',
]
####################################################################################################
import os
from ..Notation.Stave import StaveKeys, Stave, StavePair
from ..Theory.Pitch import Pitch, PitchInterval
from .Instrument import Instrument, InstrumentWriting, InstrumentTransposition, Ambitus
####################################################################################################
[docs]class InstrumentDatabase:
___instance__ = None
##############################################
[docs] @classmethod
def instance(cls):
if cls.___instance__ is None:
yaml_path = os.path.join(os.path.dirname(__file__), 'instruments.yml')
cls.___instance__ = cls(yaml_path)
return cls.___instance__
##############################################
[docs] def __init__(self, yaml_path):
self._yaml_path = yaml_path
self._instruments = {}
self._load()
##############################################
[docs] def _load(self):
import yaml
with open(self._yaml_path, 'r') as fh:
data = yaml.load(fh)
for family, family_dict in data.items():
if family_dict is not None:
for instrument_name, instrument_dict in family_dict.items():
self._load_instrument(family, instrument_name, instrument_dict)
##############################################
[docs] @staticmethod
def _to_pitch_interval(string):
lower_pitch, upper_pitch = [Pitch(x) for x in string.split()]
return PitchInterval(lower_pitch, upper_pitch)
##############################################
[docs] def _load_instrument(self, family, instrument_name, instrument_dict):
writings = []
if 'writings' in instrument_dict:
for name, writing_data in instrument_dict['writings'].items():
writing = self._load_writing(name, writing_data)
if writing:
writings.append(writing)
else:
writing = self._load_writing('default', instrument_dict)
if writing:
writings.append(writing)
kwargs = {}
transposition_data = instrument_dict.get('transposition', False)
if transposition_data:
kwargs['transposition'] = [InstrumentTransposition(name, pitch)
for name, pitch in transposition_data]
if 'ambitus' in instrument_dict:
kwargs['ambitus'] = [Ambitus(name, self._to_pitch_interval(string))
for name, string in instrument_dict['ambitus'].items()]
instrument = Instrument(name=instrument_name,
family=family,
writings=writings,
**kwargs,
)
self._instruments[instrument_name] = instrument
##############################################
[docs] def _load_writing(self, name, data):
try:
keys = [StaveKeys[x] for x in data['staff key'].split()]
if len(keys) == 1:
stave = Stave(keys[0])
else:
stave = StavePair(keys[0], keys[1])
pitch_interval = self._to_pitch_interval(data['written range'])
return InstrumentWriting(name, stave, pitch_interval)
except KeyError:
return None
##############################################
@property
def instruments(self):
return self._instruments.keys()
##############################################
[docs] def __getitem__(self, instrument):
return self._instruments[instrument]