def getWorkList(workName, movementNumber=None, extList=None): '''Search the corpus and return a list of works, always in a list. If no matches are found, an empty list is returned. >>> len(getWorkList('beethoven/opus18no1')) 8 >>> len(getWorkList('beethoven/opus18no1', 1)) 2 >>> len(getWorkList('beethoven/opus18no1', 1, '.krn')) 1 >>> len(getWorkList('beethoven/opus18no1', 1, '.xml')) 1 >>> len(getWorkList('beethoven/opus18no1', 0, '.xml')) 0 ''' if not common.isListLike(extList): extList = [extList] paths = getPaths(extList) post = [] # permit workName to be a list of paths/branches if common.isListLike(workName): workName = os.path.sep.join(workName) # replace with os-dependent separators workSlashes = workName.replace('/', os.path.sep) for path in paths: if workName.lower() in path.lower(): post.append(path) elif workSlashes.lower() in path.lower(): post.append(path) post.sort() postMvt = [] if movementNumber is not None and len(post) > 0: movementStrList = ['movement%s' % movementNumber] for fp in post: for movementStr in movementStrList: if movementStr.lower() in fp.lower(): postMvt.append(fp) if len(postMvt) == 0: pass # return an empty list else: postMvt = post if len(postMvt) == 0: return [] else: return postMvt
def getWork(workName, movementNumber=None, fileExtensions=None): ''' Search the corpus, then the virtual corpus, for a work, and return a file path or URL. N.B. does not parse the work: but it's suitable for passing to converter.parse. This method will return either a list of file paths or, if there is a single match, a single file path. If no matches are found an Exception is raised. :: >>> import os >>> from music21 import corpus >>> a = corpus.getWork('opus74no2', 4) >>> a.endswith(os.path.sep.join([ ... 'haydn', 'opus74no2', 'movement4.mxl'])) True :: >>> a = corpus.getWork(['haydn', 'opus74no2', 'movement4.xml']) >>> a.endswith(os.path.sep.join([ ... 'haydn', 'opus74no2', 'movement4.mxl'])) True :: >>> trecentoFiles = corpus.getWork('trecento') >>> len(trecentoFiles) > 100 and len(trecentoFiles) < 200 True ''' if not common.isListLike(fileExtensions): fileExtensions = [fileExtensions] results = getWorkList(workName, movementNumber, fileExtensions) if len(results) == 0: if common.isListLike(workName): workName = os.path.sep.join(workName) if workName.endswith(".xml"): # might be compressed MXL file newWorkName = workName[0:len(workName) - 4] + ".mxl" return getWork(newWorkName, movementNumber, fileExtensions) results = getVirtualWorkList(workName, movementNumber, fileExtensions) if len(results) == 1: return results[0] elif len(results) == 0: raise CorpusException( 'Could not find a file/url that met these criteria') return results
def __init__(self, target=()): super().__init__() if not common.isListLike(target): target = (target, ) self.target = target self.numToFind = len(target)
def __init__(self, classList=()): super().__init__() if not common.isListLike(classList): classList = (classList, ) self.classList = classList
def __setitem__(self, key, value): '''Dictionary-like setting. Changes are made and written to the user configuration file. >>> us = environment.UserSettings() >>> us['musicxmlPath'] = 'asdfwerasdffasdfwer' Traceback (most recent call last): UserSettingsException: attempting to set a path that does not exist: asdfwerasdffasdfwer >>> us['localCorpusPath'] = '/path/to/local' Traceback (most recent call last): UserSettingsException: attempting to set a path that does not exist: /path/to/local ''' # NOTE: testing setting of any UserSettings key will result # in a change in your local preferences files # before setting value, see if this is a path and test existence # this will accept localCorpusPath if key in self._environment.getKeysToPaths(): # try to expand user if found; otherwise return unaltered value = os.path.expanduser(value) if not os.path.exists(value): raise UserSettingsException( 'attempting to set a path that does not exist: %s' % value) # when setting a local corpus setting, if not a list, append elif key in ['localCorpusSettings']: if not common.isListLike(value): raise UserSettingsException( 'localCorpusSettings must be provided as a list.') # location specific, cannot test further self._environment.__setitem__(key, value) self._environment.write()
def _translateExtensions( self, fileExtensions=None, expandExtensions=True, ): # noinspection PyShadowingNames ''' Utility to get default extensions, or, optionally, expand extensions to all known formats. >>> coreCorpus = corpus.corpora.CoreCorpus() >>> for extension in coreCorpus._translateExtensions(): ... extension ... '.abc' '.capx' '.mid' '.midi' '.xml' '.mxl' '.musicxml' '.md' '.musedata' '.zip' '.krn' '.rntxt' '.rntext' '.romantext' '.rtxt' '.nwctxt' '.nwc' >>> coreCorpus._translateExtensions('.mid', False) ['.mid'] >>> coreCorpus._translateExtensions('.mid', True) ['.mid', '.midi'] It does not matter if you choose a canonical name or not, the output is the same: >>> coreCorpus._translateExtensions('.musicxml', True) ['.xml', '.mxl', '.musicxml'] >>> coreCorpus._translateExtensions('.xml', True) ['.xml', '.mxl', '.musicxml'] ''' if not common.isListLike(fileExtensions): fileExtensions = [fileExtensions] if len(fileExtensions) == 1 and fileExtensions[0] is None: fileExtensions = Corpus._allExtensions elif expandExtensions: expandedExtensions = [] for extension in fileExtensions: allInputExtensions = common.findInputExtension(extension) if allInputExtensions is None: pass else: expandedExtensions += allInputExtensions return expandedExtensions return fileExtensions
def parse(value, *args, **keywords): '''Given a file path, encoded data in a Python string, or a URL, attempt to parse the item into a Stream. Note: URL downloading will not happen automatically unless the user has set their Environment "autoDownload" preference to "allow". >>> s = parse(["E4 r f# g=lastG trip{b-8 a g} c", "3/4"]) >>> s = parse("E8 f# g#' G f g# g G#", "2/4") ''' #environLocal.printDebug(['attempting to parse()', value]) if 'forceSource' in keywords.keys(): forceSource = keywords['forceSource'] else: forceSource = False if common.isListLike(value) or len(args) > 0: # tiny notation list if len(args) > 0: # add additional args to a lost value = [value] + list(args) return parseData(value) elif os.path.exists(value): return parseFile(value, forceSource=forceSource) elif value.startswith('http://'): # its a url; may need to broaden these criteria return parseURL(value, forceSource=forceSource) else: return parseData(value)
def parseWork(workName, movementNumber=None, number=None, extList=None, forceSource=False): '''Search the corpus, then the virtual corpus, for a work, and return a parsed :class:`music21.stream.Stream`. If `movementNumber` is defined, and a movement is included in the corpus, that movement will be returned. If `number` is defined, and the work is a collection with multiple components, that work number will be returned. If `forceSource` is True, the original file will always be loaded and pickled files, if available, will be ignored. >>> aStream = parseWork('opus74no1/movement3') ''' if not common.isListLike(extList): extList = [extList] post = getWorkList(workName, movementNumber, extList) #environLocal.printDebug(['result of getWorkList()', post]) if len(post) == 0: post = getVirtualWorkList(workName, movementNumber, extList) if len(post) == 1: fp = post[0] elif len(post) == 0: raise CorpusException("Could not find a work that met this criteria") else: # greater than zero: fp = post[0] # get first return converter.parse(fp, forceSource=forceSource, number=number)
def parseFile(self, fp, number=None): ''' parse fp and number ''' from music21 import converter from music21 import musedata as musedataModule from music21.musedata import translate as musedataTranslate mdw = musedataModule.MuseDataWork() af = converter.ArchiveManager(fp) #environLocal.printDebug(['ConverterMuseData: parseFile', fp, af.isArchive()]) # for dealing with one or more files if fp.endswith('.zip') or af.isArchive(): #environLocal.printDebug(['ConverterMuseData: found archive', fp]) # get data will return all data from the zip as a single string for partStr in af.getData(dataFormat='musedata'): #environLocal.printDebug(['partStr', len(partStr)]) mdw.addString(partStr) else: if os.path.isdir(fp): mdd = musedataModule.MuseDataDirectory(fp) fpList = mdd.getPaths() elif not common.isListLike(fp): fpList = [fp] else: fpList = fp for fp in fpList: mdw.addFile(fp) #environLocal.printDebug(['ConverterMuseData: mdw file count', len(mdw.files)]) musedataTranslate.museDataWorkToStreamScore(mdw, self.stream)
def __setitem__(self, key, value): """ Dictionary-like setting. Changes are made and written to the user configuration file. >>> from music21 import environment >>> us = environment.UserSettings() >>> us['musicxmlPath'] = 'asdfwerasdffasdfwer' Traceback (most recent call last): UserSettingsException: attempting to set a path that does not exist: asdfwerasdffasdfwer >>> us['localCorpusPath'] = '/path/to/local' Traceback (most recent call last): UserSettingsException: attempting to set a path that does not exist: /path/to/local """ # NOTE: testing setting of any UserSettings key will result # in a change in your local preferences files # before setting value, see if this is a path and test existence # this will accept localCorpusPath if key in self._environment.getKeysToPaths(): # try to expand user if found; otherwise return unaltered if value is not None: value = os.path.expanduser(value) if not os.path.exists(value): raise UserSettingsException("attempting to set a path that does not exist: {}".format(value)) # when setting a local corpus setting, if not a list, append elif key == "localCorpusSettings": if not common.isListLike(value): raise UserSettingsException("localCorpusSettings must be provided as a list.") # location specific, cannot test further self._environment.__setitem__(key, value) self._environment.write()
def __init__(self, target=()): super().__init__() if not common.isListLike(target): target = (target,) self.target = target self.numToFind = len(target)
def getWorkList( self, workName, movementNumber=None, fileExtensions=None, ): ''' Given a work name, search all virtual works and return a list of URLs for any matches. >>> from music21 import corpus >>> virtualCorpus = corpus.VirtualCorpus() >>> virtualCorpus.getWorkList('bach/bwv1007/prelude') ['http://kern.ccarh.org/cgi-bin/ksdata?l=cc/bach/cello&file=bwv1007-01.krn&f=xml'] >>> virtualCorpus.getWorkList('junk') [] ''' if not common.isListLike(fileExtensions): fileExtensions = [fileExtensions] for obj in VirtualCorpus._virtual_works: if obj.corpusPath is not None and \ workName.lower() in obj.corpusPath.lower(): return obj.getUrlByExt(fileExtensions) return []
def getDoc(self, partName): element = self.getElement(partName) if hasattr(self.srcNameEval, '_DOC_ATTR'): docAttr = self.srcNameEval._DOC_ATTR else: docAttr = {} match = None if partName in docAttr.keys(): match = docAttr[partName] # if its an undocumented public attribute and basic python # data structure, we do not want to show that documentation elif (element.kind in ['data'] and ( common.isStr(element.object) or common.isListLike(element.object) or common.isNum(element.object) )): pass else: try: match = element.object.__doc__ except AttributeError: match = None if match == None: return NO_DOC else: return match
def __init__(self, classList=()): super().__init__() if not common.isListLike(classList): classList = (classList,) self.classList = classList
def getWork(workName, movementNumber=None, extList=None): '''Search the corpus, then the virtual corpus, for a work. This method will return either a list of file paths or, if there is a single match, a single file path. If no matches are found an Exception is raised. >>> import os >>> a = getWork('opus74no2', 4) >>> a.endswith(os.path.sep.join(['haydn', 'opus74no2', 'movement4.xml'])) True >>> a = getWork(['haydn', 'opus74no2', 'movement4.xml']) >>> a.endswith(os.path.sep.join(['haydn', 'opus74no2', 'movement4.xml'])) True ''' if not common.isListLike(extList): extList = [extList] post = getWorkList(workName, movementNumber, extList) if len(post) == 0: post = getVirtualWorkList(workName, movementNumber, extList) if len(post) == 1: return post[0] elif len(post) == 0: raise CorpusException("Could not find a file/url that met this criteria") else: # return a list return post
def getWorkList( self, workName, movementNumber=None, fileExtensions=None, ): ''' Given a work name, search all virtual works and return a list of URLs for any matches. >>> virtualCorpus = corpus.corpora.VirtualCorpus() >>> virtualCorpus.getWorkList('bach/bwv1007/prelude') ['http://kern.ccarh.org/cgi-bin/ksdata?l=cc/bach/cello&file=bwv1007-01.krn&f=xml'] >>> virtualCorpus.getWorkList('junk') [] ''' if not common.isListLike(fileExtensions): fileExtensions = [fileExtensions] for obj in VirtualCorpus._virtual_works: if obj.corpusPath is not None and workName.lower( ) in obj.corpusPath.lower(): return obj.getUrlByExt(fileExtensions) return []
def _setPitches(self, value): if common.isListLike(value): if 'Pitch' in value[0].classes: self.pitch = value[0] else: raise NoteException('must provide a list containing a Pitch, not: %s' % value) else: raise NoteException('cannot set pitches with provided object: %s' % value)
def getPaths(extList=None, expandExtensions=True): '''Get all paths in the corpus that match a known extension, or an extenion provided by an argument. If `expandExtensions` is True, a format for an extension, and related extensions, will replaced by all known input extensions. This is convenient when an input format might match for multiple extensions. >>> a = getPaths() >>> len(a) > 30 True >>> a = getPaths('krn') >>> len(a) >= 4 True >>> a = getPaths('abc') >>> len(a) >= 10 True ''' if not common.isListLike(extList): extList = [extList] if extList == [None]: extList = _ALL_EXTENSIONS elif expandExtensions: extMod = [] for e in extList: extMod += common.findInputExtension(e) extList = extMod #environLocal.printDebug(['getting paths with extensions:', extList]) paths = [] for moduleName in MODULES: if not hasattr(moduleName, '__path__'): # when importing a package name (a directory) the moduleName # may be a list of all paths contained within the package # this seems to be dependent on the context of the call: # from the command line is different than from the interpreter dirListing = moduleName else: # returns a list with one or more paths # the first is the path to the directory that contains xml files dir = moduleName.__path__[0] dirListing = [os.path.join(dir, x) for x in os.listdir(dir)] for fp in dirListing: if fp in paths: continue match = False for ext in extList: if fp.endswith(ext): match = True break if match: if fp not in paths: paths.append(fp) return paths
def cacheMetadata( corpusNames=('local', 'core', 'virtual'), useMultiprocessing=True, ): ''' Cache metadata from corpuses in `corpusNames` as local cache files: Call as ``metadata.cacheMetadata()`` ''' from music21 import corpus from music21 import metadata if not common.isListLike(corpusNames): corpusNames = (corpusNames,) timer = common.Timer() timer.start() # store list of file paths that caused an error failingFilePaths = [] # the core cache is based on local files stored in music21 # virtual is on-line for corpusName in corpusNames: if corpusName == 'core': metadataBundle = metadata.MetadataBundle.fromCoreCorpus() paths = corpus.getCorePaths() useCorpus = True elif corpusName == 'local': metadataBundle = metadata.MetadataBundle.fromLocalCorpus() paths = corpus.getLocalPaths() useCorpus = False elif corpusName == 'virtual': metadataBundle = metadata.MetadataBundle.fromVirtualCorpus() paths = corpus.getVirtualPaths() useCorpus = False else: message = 'invalid corpus name provided: {0!r}'.format(corpusName) raise MetadataCacheException(message) message = 'metadata cache: starting processing of paths: {0}'.format( len(paths)) environLocal.printDebug(message) failingFilePaths += metadataBundle.addFromPaths( paths, useCorpus=useCorpus, useMultiprocessing=useMultiprocessing, ) message = 'cache: writing time: {0} md items: {1}'.format( timer, len(metadataBundle)) environLocal.printDebug(message) del metadataBundle message = 'cache: final writing time: {0} seconds'.format(timer) environLocal.printDebug(message) for failingFilePath in failingFilePaths: message = 'path failed to parse: {0}'.format(failingFilePath) environLocal.printDebug(message)
def cacheMetadata(corpusNames=('local',)): ''' Rebuild the metadata cache. ''' if not common.isListLike(corpusNames): corpusNames = [corpusNames] for name in corpusNames: corpora.Corpus._metadataBundles[name] = None metadata.cacheMetadata(corpusNames)
def cacheMetadata(corpusNames=('local', )): ''' Rebuild the metadata cache. ''' if not common.isListLike(corpusNames): corpusNames = [corpusNames] for name in corpusNames: corpora.Corpus._metadataBundles[name] = None metadata.cacheMetadata(corpusNames)
def cacheMetadata( corpusNames=('local', 'core', 'virtual'), useMultiprocessing=True, ): ''' Cache metadata from corpuses in `corpusNames` as local cache files: Call as ``metadata.cacheMetadata()`` ''' from music21 import corpus from music21 import metadata if not common.isListLike(corpusNames): corpusNames = (corpusNames, ) timer = common.Timer() timer.start() # store list of file paths that caused an error failingFilePaths = [] # the core cache is based on local files stored in music21 # virtual is on-line for corpusName in corpusNames: if corpusName == 'core': metadataBundle = metadata.MetadataBundle.fromCoreCorpus() paths = corpus.getCorePaths() useCorpus = True elif corpusName == 'local': metadataBundle = metadata.MetadataBundle.fromLocalCorpus() paths = corpus.getLocalPaths() useCorpus = False elif corpusName == 'virtual': metadataBundle = metadata.MetadataBundle.fromVirtualCorpus() paths = corpus.getVirtualPaths() useCorpus = False else: message = 'invalid corpus name provided: {0!r}'.format(corpusName) raise MetadataCacheException(message) message = 'metadata cache: starting processing of paths: {0}'.format( len(paths)) environLocal.printDebug(message) failingFilePaths += metadataBundle.addFromPaths( paths, useCorpus=useCorpus, useMultiprocessing=useMultiprocessing, ) message = 'cache: writing time: {0} md items: {1}'.format( timer, len(metadataBundle)) environLocal.printDebug(message) del metadataBundle message = 'cache: final writing time: {0} seconds'.format(timer) environLocal.printDebug(message) for failingFilePath in failingFilePaths: message = 'path failed to parse: {0}'.format(failingFilePath) environLocal.printDebug(message)
def _dynamicToWeight(targets): # permit a stream if hasattr(targets, 'isStream') and targets.isStream: pass elif not common.isListLike(targets): targets = [targets] summation = 0 for e in targets: # a Stream summation += e.volumeScalar # for dynamics return summation / float(len(target))
def __setitem__(self, key, value): #saxutils.escape # used for escaping strings going to xml # with unicode encoding # http://www.xml.com/pub/a/2002/11/13/py-xml.html?page=2 # saxutils.escape(msg).encode('UTF-8') # add local corpus path as a key #if six.PY3 and isinstance(value, bytes): # value = value.decode(errors='replace') if 'path' in key.lower() and value is not None: value = common.cleanpath(value) if key not in self._ref: if key != 'localCorpusPath': raise EnvironmentException('no preference: %s' % key) if value == '': value = None # always replace '' with None valid = False if key == 'showFormat': value = value.lower() if value in common.VALID_SHOW_FORMATS: valid = True elif key == 'writeFormat': value = value.lower() if value in common.VALID_WRITE_FORMATS: valid = True elif key == 'autoDownload': value = value.lower() if value in common.VALID_AUTO_DOWNLOAD: valid = True elif key == 'localCorpusSettings': # needs to be a list of strings for now if common.isListLike(value): valid = True else: # temporarily not validating other preferences valid = True if not valid: raise EnvironmentException( '{} is not an acceptable value for preference: {}'.format( value, key)) # need to escape problematic characters for xml storage if isinstance(value, six.string_types): value = saxutils.escape(value) #.encode('UTF-8') # set value if key == 'localCorpusPath': # only add if unique #value = xmlnode.fixBytes(value) if value not in self._ref['localCorpusSettings']: # check for malicious values here self._ref['localCorpusSettings'].append(value) else: self._ref[key] = value
def _translateExtensions( self, fileExtensions=None, expandExtensions=True, ): ''' Utility to get default extensions, or, optionally, expand extensions to all known formats. >>> from music21 import corpus >>> coreCorpus = corpus.CoreCorpus() >>> for extension in coreCorpus._translateExtensions(): ... extension ... '.abc' '.capx' '.mid' '.midi' '.xml' '.mxl' '.mx' '.musicxml' '.md' '.musedata' '.zip' '.krn' '.rntxt' '.rntext' '.romantext' '.rtxt' '.nwctxt' '.nwc' >>> coreCorpus._translateExtensions('.mid', False) ['.mid'] >>> coreCorpus._translateExtensions('.mid', True) ['.mid', '.midi'] ''' if not common.isListLike(fileExtensions): fileExtensions = [fileExtensions] if fileExtensions == [None]: fileExtensions = Corpus._allExtensions elif expandExtensions: expandedExtensions = [] for extension in fileExtensions: allInputExtensions = common.findInputExtension(extension) if allInputExtensions is None: pass else: expandedExtensions += allInputExtensions return expandedExtensions return fileExtensions
def getWork( workName, movementNumber=None, fileExtensions=None, ): ''' this parse method is called from `corpus.parse()` and does nothing differently from it. Searches all corpora for a file that matches the name and returns it parsed. ''' addXMLWarning = False workNameJoined = workName mxlWorkName = workName if workName in (None, ''): raise CorpusException('a work name must be provided as an argument') if not common.isListLike(fileExtensions): fileExtensions = [fileExtensions] if common.isIterable(workName): workNameJoined = os.path.sep.join(workName) if workNameJoined.endswith(".xml"): # might be compressed MXL file mxlWorkName = os.path.splitext(workNameJoined)[0] + ".mxl" addXMLWarning = True filePaths = None for corpusObject in iterateCorpora(): workList = corpusObject.getWorkList(workName, movementNumber, fileExtensions) if not workList and addXMLWarning: workList = corpusObject.getWorkList(mxlWorkName, movementNumber, fileExtensions) if not workList: continue if len(workList) >= 1: filePaths = workList break if filePaths is None: warningMessage = 'Could not find a' if addXMLWarning: warningMessage += 'n xml or mxl' warningMessage += ' work that met this criterion: {0};'.format( workName) warningMessage += ' if you are searching for a file on disk, ' warningMessage += 'use "converter" instead of "corpus".' raise CorpusException(warningMessage) else: if len(filePaths) == 1: return filePaths[0] else: return filePaths
def getWork(workName, movementNumber=None, fileExtensions=None, ): ''' this parse method is called from `corpus.parse()` and does nothing differently from it. Searches all corpora for a file that matches the name and returns it parsed. ''' addXMLWarning = False workNameJoined = workName mxlWorkName = workName if workName in (None, ''): raise CorpusException( 'a work name must be provided as an argument') if not common.isListLike(fileExtensions): fileExtensions = [fileExtensions] if common.isIterable(workName): workNameJoined = os.path.sep.join(workName) if workNameJoined.endswith(".xml"): # might be compressed MXL file mxlWorkName = os.path.splitext(workNameJoined)[0] + ".mxl" addXMLWarning = True filePaths = None for corpusObject in iterateCorpora(): workList = corpusObject.getWorkList(workName, movementNumber, fileExtensions) if not workList and addXMLWarning: workList = corpusObject.getWorkList(mxlWorkName, movementNumber, fileExtensions) if not workList: continue if len(workList) >= 1: filePaths = workList break if filePaths is None: warningMessage = 'Could not find a' if addXMLWarning: warningMessage += 'n xml or mxl' warningMessage += ' work that met this criterion: {0};'.format(workName) warningMessage += ' if you are searching for a file on disk, ' warningMessage += 'use "converter" instead of "corpus".' raise CorpusException(warningMessage) else: if len(filePaths) == 1: return filePaths[0] else: return filePaths
def midiEventsToKeySignature(eventList): '''Convert a single MIDI event into a music21 TimeSignature object. >>> from music21 import * >>> mt = midi.MidiTrack(1) >>> me1 = midi.MidiEvent(mt) >>> me1.type = "KEY_SIGNATURE" >>> me1.data = midi.putNumbersAsList([2, 0]) # d major >>> ks = midiEventsToKeySignature(me1) >>> ks <music21.key.KeySignature of 2 sharps> >>> me2 = midi.MidiEvent(mt) >>> me2.type = "KEY_SIGNATURE" >>> me2.data = midi.putNumbersAsList([-2, 0]) # b- major >>> me2.data '\\xfe\\x00' >>> midi.getNumbersAsList(me2.data) [254, 0] >>> ks = midiEventsToKeySignature(me2) >>> ks <music21.key.KeySignature of 2 flats> ''' # This meta event is used to specify the key (number of sharps or flats) and scale (major or minor) of a sequence. A positive value for the key specifies the number of sharps and a negative value specifies the number of flats. A value of 0 for the scale specifies a major key and a value of 1 specifies a minor key. from music21 import key if not common.isListLike(eventList): event = eventList else: # get the second event; first is delta time event = eventList[1] post = midiModule.getNumbersAsList(event.data) if post[0] > 12: # flip around 256 sharpCount = post[0] - 256 # need negative values else: sharpCount = post[0] environLocal.printDebug(['midiEventsToKeySignature', post, sharpCount]) # first value is number of sharp, or neg for number of flat ks = key.KeySignature(sharpCount) if post[1] == 0: ks.mode = 'major' if post[1] == 1: ks.mode = 'minor' return ks
def _fillData(self): ''' >>> from music21 import pitch >>> a = PartitionedModule(pitch) >>> len(a.names) == len(a._data) True >>> a.namesOrdered ['Pitch', 'Accidental'] >>> a.names[0] 'Pitch' >>> a.names[0] == a._data[0].name True ''' post = [] for name in self.names: objName = '%s.%s' % (self.srcNameStr, name) obj = eval(objName) # skip for now homecls = self.srcNameEval if hasattr(obj, '__module__'): if self.srcNameStr not in obj.__module__: homecls = obj.__module__ # get kind if isinstance(obj, types.ModuleType): kind = 'module' elif (isinstance(obj, types.StringTypes) or isinstance(obj, types.DictionaryType) or isinstance(obj, types.ListType) or common.isNum(obj) or common.isListLike(obj) or isinstance(obj, type(MOCK_RE))): kind = 'data' elif isinstance(obj, types.FunctionType): kind = 'function' elif isinstance(obj, types.TypeType): kind = 'class' elif isinstance(obj, environment.Environment): kind = 'data' # skip environment object else: environLocal.printDebug(['cannot process module level name: %s' % self.srcNameStr]) kind = None post.append(inspect.Attribute(name, kind, homecls, obj)) return post
def cacheMetadata(domainList=['local','core', 'virtual']): '''The core cache is all locally-stored corpus files. ''' from music21 import corpus, metadata if not common.isListLike(domainList): domainList = [domainList] t = common.Timer() t.start() # store list of file paths that caused an error fpError = [] # the core cache is based on local files stored in music21 # virtual is on-line for domain in domainList: # the domain passed here becomes the name of the bundle # determines the file name of the json bundle mdb = metadata.MetadataBundle(domain) if domain == 'virtual': getPaths = corpus.getVirtualPaths elif domain == 'core': getPaths = corpus.getCorePaths elif domain == 'local': getPaths = corpus.getLocalPaths else: raise MetadataCacheException('invalid domain provided: %s' % domain) paths = getPaths() environLocal.warn([ 'metadata cache: starting processing of paths:', len(paths)]) #mdb.addFromPaths(paths[-3:]) # returns any paths that failed to load fpError += mdb.addFromPaths(paths, printDebugAfter = 50) #print mdb.storage mdb.write() # will use a default file path based on domain environLocal.warn(['cache: writing time:', t, 'md items:', len(mdb.storage)]) del mdb environLocal.warn(['cache: final writing time:', t]) for fp in fpError: environLocal.warn('path failed to parse: %s' % fp)
def getVirtualWorkList(workName, movementNumber=None, extList=None): '''Given a work name, search all virtual works and return a list of URLs for any matches. >>> getVirtualWorkList('bach/bwv1007/prelude') ['http://kern.ccarh.org/cgi-bin/ksdata?l=cc/bach/cello&file=bwv1007-01.krn&f=xml'] >>> getVirtualWorkList('junk') [] ''' if not common.isListLike(extList): extList = [extList] for obj in VIRTUAL: if obj.corpusPath != None and workName.lower() in obj.corpusPath.lower(): return obj.getUrlByExt(extList) return []
def __setitem__(self, key, value): #saxutils.escape # used for escaping strings going to xml # with unicode encoding # http://www.xml.com/pub/a/2002/11/13/py-xml.html?page=2 # saxutils.escape(msg).encode('UTF-8') # add local corpus path as a key if key not in self._ref.keys() + ['localCorpusPath']: raise EnvironmentException('no preference: %s' % key) if value == '': value = None # always replace '' with None valid = False if key == 'showFormat': value = value.lower() if value in common.VALID_SHOW_FORMATS: valid = True elif key == 'writeFormat': value = value.lower() if value in common.VALID_WRITE_FORMATS: valid = True elif key == 'autoDownload': value = value.lower() if value in common.VALID_AUTO_DOWNLOAD: valid = True elif key == 'localCorpusSettings': # needs to be a list of strings for now if common.isListLike(value): valid = True else: # temporarily not validating other preferences valid = True if not valid: raise EnvironmentException('%s is not an acceptable value for preference: %s' % (value, key)) # need to escape problematic characters for xml storage if common.isStr(value): value = xml.sax.saxutils.escape(value).encode('UTF-8') # set value if key == 'localCorpusPath': # only add if unique if value not in self._ref['localCorpusSettings']: # check for malicious values here self._ref['localCorpusSettings'].append(value) else: self._ref[key] = value
def getComposerDir(composerName): '''Given the name of a composer, get the path to the top-level directory of that composer >>> import os >>> a = getComposerDir('beethoven') >>> a.endswith(os.path.join('corpus', os.sep, 'beethoven')) True >>> a = getComposerDir('bach') >>> a.endswith(os.path.join('corpus', os.sep, 'bach')) True >>> a = getComposerDir('mozart') >>> a.endswith(os.path.join('corpus', os.sep, 'mozart')) True >>> a = getComposerDir('luca') >>> a.endswith(os.path.join('corpus', os.sep, 'luca')) True ''' # TODO: have this also search COMPOSERS references for full names match = None for moduleName in MODULES: if common.isListLike(moduleName): candidate = moduleName[0] else: candidate = str(moduleName) if composerName.lower() not in candidate.lower(): continue # might also slook at .__file__ if not hasattr(moduleName, '__path__'): # its a list of files dirCandidate = moduleName[0] while True: dir = os.path.dirname(dirCandidate) if dir.lower().endswith(composerName.lower()): break else: dirCandidate = dir if dirCandidate.count(os.sep) < 2: # done enough checks break else: dir = moduleName.__path__[0] # last check if dir.lower().endswith(composerName.lower()): match = dir break return match
def getPaths(extList=None): '''Get all paths in the corpus that match a known extension, or an extenion provided by an argument. >>> a = getPaths() >>> len(a) > 30 True >>> a = getPaths('krn') >>> len(a) >= 4 True ''' if not common.isListLike(extList): extList = [extList] if extList == [None]: extList = (common.findInputExtension('lily') + common.findInputExtension('musicxml') + common.findInputExtension('humdrum')) #environLocal.printDebug(['getting paths with extensions:', extList]) paths = [] for moduleName in MODULES: if not hasattr(moduleName, '__path__'): # when importing a package name (a directory) the moduleName # may be a list of all paths contained within the package # this seems to be dependent on the context of the call: # from the command line is different than from the interpreter dirListing = moduleName else: # returns a list with one or more paths # the first is the path to the directory that contains xml files dir = moduleName.__path__[0] dirListing = [os.path.join(dir, x) for x in os.listdir(dir)] for fp in dirListing: if fp in paths: continue match = False for ext in extList: if fp.endswith(ext): match = True break if match: if fp not in paths: paths.append(fp) return paths
def getUrlByExt(self, extList=None): '''Given a request for an extension, find a best match for a URL from the list of known URLs. If ext is None, return the first URL. ''' if not common.isListLike(extList): extList = [extList] if extList is None or extList == [None]: return [self.urlList[0]] # return a list of all post = [] for ext in extList: for url in self.urlList: unused_format, extFound = common.findFormatExtURL(url) # environLocal.printDebug([extFound, ext]) if extFound == ext: post.append(url) return post # no match
def getUrlByExt(self, extList=None): '''Given a request for an extension, find a best match for a URL from the list of known URLs. If ext is None, return the first URL. ''' if not common.isListLike(extList): extList = [extList] if extList == None or extList == [None]: return [self.urlList[0]] # return a list of all post = [] for ext in extList: for url in self.urlList: unused_format, extFound = common.findFormatExtURL(url) #environLocal.printDebug([extFound, ext]) if extFound == ext: post.append(url) return post # no match
def _setMX(self, mxKeyList): """Given a mxKey object or keyList, load internal attributes >>> a = musicxml.Key() >>> a.set('fifths', 5) >>> b = KeySignature() >>> b.mx = [a] >>> b.sharps 5 """ if not common.isListLike(mxKeyList): mxKey = mxKeyList else: # there may be more than one if we have more staffs per part mxKey = mxKeyList[0] self.sharps = int(mxKey.get("fifths")) mxMode = mxKey.get("mode") if mxMode != None: self.mode = mxMode
def parseData(self, dataStr): '''Given raw data, determine format and parse into a music21 Stream. ''' format = None if common.isListLike(dataStr): format = 'tinyNotation' if format == None: # its a string dataStr = dataStr.lstrip() if dataStr.startswith('<?xml'): format = 'musicxml' elif dataStr.startswith('!!!') or dataStr.startswith('**'): format = 'humdrum' else: raise ConverterException('no such format found for: %s' % dataStr) self._setConverter(format) self._converter.parseData(dataStr)
def getVirtualPaths(extList=None): '''Get all paths in the virtual corpus that match a known extension. An extension of None will return all known extensions. >>> len(getVirtualPaths()) > 6 True ''' if not common.isListLike(extList): extList = [extList] if extList == [None]: extList = _ALL_EXTENSIONS paths = [] for obj in VIRTUAL: if obj.corpusPath != None: for ext in extList: #environLocal.printDebug([obj.corpusPath, ext]) post = obj.getUrlByExt(ext) for part in post: if part not in paths: paths.append(part) return paths
def add(self, spanner, component): '''Add a spanner and one or more component references. The `spanner` is the object represent the connections, such as a Slur or GroupBracket. The `component` is one or more (a list) objects, such as Note or Part. If the spanner already exists, the component will be added to the components list. >>> from music21 import * >>> class TestMock(object): pass >>> tm1 = TestMock() >>> n1 = note.Note('c2') >>> n2 = note.Note('g3') >>> sp1 = Spanners() >>> sp1.add(tm1, [n1, n2]) >>> len(sp1) 1 ''' idSpanner = id(spanner) if idSpanner not in self.keys(): self._storage[idSpanner] = (common.wrapWeakref(spanner), []) self._idRef[idSpanner] = [] # just a list refPair = self._storage[idSpanner] idList = self._idRef[idSpanner] # presently this does not look for redundant entries # store component as weak ref if common.isListLike(component): bundle = component else: # just append one bundle = [component] for sub in bundle: # permit a lost of spanners refPair[1].append(common.wrapWeakref(sub)) idList.append(id(sub))
def getWorkList( self, workName, movementNumber=None, fileExtensions=None, ): r''' Search the corpus and return a list of filenames of works, always in a list. If no matches are found, an empty list is returned. >>> from music21 import corpus >>> coreCorpus = corpus.corpora.CoreCorpus() # returns 1 even though there is a '.mus' file, which cannot be read... >>> len(coreCorpus.getWorkList('cpebach/h186')) 1 >>> len(coreCorpus.getWorkList('cpebach/h186', None, '.xml')) 1 >>> len(coreCorpus.getWorkList('schumann_clara/opus17', 3)) 1 >>> len(coreCorpus.getWorkList('schumann_clara/opus17', 2)) 0 Make sure that 'verdi' just gets the single Verdi piece and not the Monteverdi pieces: >>> len(coreCorpus.getWorkList('verdi')) 1 ''' if not common.isListLike(fileExtensions): fileExtensions = [fileExtensions] paths = self.getPaths(fileExtensions) results = [] workPath = pathlib.PurePath(workName) workPosix = workPath.as_posix().lower() # find all matches for the work name # TODO: this should match by path component, not just # substring for path in paths: if workPosix in path.as_posix().lower(): results.append(path) if results: # more than one matched...use more stringent criterion: # must have a slash before the name previousResults = results results = [] for path in previousResults: if '/' + workPosix in path.as_posix().lower(): results.append(path) if not results: results = previousResults movementResults = [] if movementNumber is not None and results: # store one ore more possible mappings of movement number movementStrList = [] # see if this is a pair if common.isIterable(movementNumber): movementStrList.append(''.join(str(x) for x in movementNumber)) movementStrList.append('-'.join( str(x) for x in movementNumber)) movementStrList.append('movement' + '-'.join( str(x) for x in movementNumber)) movementStrList.append('movement' + '-0'.join( str(x) for x in movementNumber)) else: movementStrList += [ '0{0}'.format(movementNumber), str(movementNumber), 'movement{0}'.format(movementNumber), ] for filePath in sorted(results): filename = filePath.name if filePath.suffix: filenameWithoutExtension = filePath.stem else: filenameWithoutExtension = None searchPartialMatch = True if filenameWithoutExtension is not None: # look for direct matches first for movementStr in movementStrList: # if movementStr.lower() in filePath.lower(): if filenameWithoutExtension.lower( ) == movementStr.lower(): movementResults.append(filePath) searchPartialMatch = False # if we have one direct match, all other matches must # be direct. this will match multiple files with different # file extensions if movementResults: continue if searchPartialMatch: for movementStr in movementStrList: if filename.startswith(movementStr.lower()): movementResults.append(filePath) if not movementResults: pass else: movementResults = results return sorted(set(movementResults))
def parseInputToPrimitive(self, inpVal): ''' Determines what format a given input is in and returns a value in that format.. First checks if it is the name of a variable defined in the parsedDataDict or the name of an allowable function. In either of these cases, it will return the actual value of the data or the actual function. Next, it will check if the string is an int, float, boolean, or none, returning the appropriate value. If it is a quoted string then it will remove the quotes on the ends and return it as a string. If it has square braces indicating a list, the inner elements will be parsed using this same function recursively. (Note that recursive lists like [1, 2, [3, 4]] are not yet supported If the input corresponds to none of these types, it is returned as a string. >>> agenda = webapps.Agenda() >>> agenda.addData("a",2) >>> agenda.addData("b",[1,2,3],"list") >>> processor = webapps.CommandProcessor(agenda) >>> processor.parseInputToPrimitive("a") 2 >>> processor.parseInputToPrimitive("b") [1, 2, 3] >>> processor.parseInputToPrimitive("1.0") 1.0 >>> processor.parseInputToPrimitive("2") 2 >>> processor.parseInputToPrimitive("True") True >>> processor.parseInputToPrimitive("False") False >>> processor.parseInputToPrimitive("None") == None True >>> processor.parseInputToPrimitive("'hi'") 'hi' >>> processor.parseInputToPrimitive("'Madam I\'m Adam'") "Madam I'm Adam" >>> processor.parseInputToPrimitive("[1,2,3]") [1, 2, 3] >>> processor.parseInputToPrimitive("[1,'hi',3.0,True, a, justAStr]") [1, 'hi', 3.0, True, 2, 'justAStr'] ''' returnVal = None if common.isNum(inpVal): return inpVal if common.isListLike(inpVal): return [self.parseInputToPrimitive(element) for element in inpVal] if not common.isStr(inpVal): self.recordError("Unknown type for parseInputToPrimitive " + str(inpVal)) strVal = inpVal strVal = strVal.strip() # removes whitespace on ends if strVal in self.parsedDataDict: # Used to specify data via variable name returnVal = self.parsedDataDict[strVal] elif strVal in availableFunctions: # Used to specify function via variable name returnVal = strVal else: try: returnVal = int(strVal) except: try: returnVal = float(strVal) except: if strVal == "True": returnVal = True elif strVal == "None": returnVal = None elif strVal == "False": returnVal = False elif strVal[0] == '"' and strVal[ -1] == '"': # Double Quoted String returnVal = strVal[1:-1] # remove quotes elif strVal[0] == "'" and strVal[ -1] == "'": # Single Quoted String returnVal = strVal[1:-1] # remove quotes elif strVal[0] == "[" and strVal[-1] == "]": # List listElements = strVal[1:-1].split( ",") # remove [] and split by commas returnVal = [ self.parseInputToPrimitive(element) for element in listElements ] else: returnVal = cgi.escape(str(strVal)) return returnVal
def makeRests(s, refStreamOrTimeRange=None, fillGaps=False, timeRangeFromBarDuration=False, inPlace=True): ''' Given a Stream with an offset not equal to zero, fill with one Rest preeceding this offset. This can be called on any Stream, a Measure alone, or a Measure that contains Voices. If `refStreamOrTimeRange` is provided as a Stream, this Stream is used to get min and max offsets. If a list is provided, the list assumed to provide minimum and maximum offsets. Rests will be added to fill all time defined within refStream. If `fillGaps` is True, this will create rests in any time regions that have no active elements. If `timeRangeFromBarDuration` is True, and the calling Stream is a Measure with a TimeSignature, the time range will be determined based on the .barDuration property. If `inPlace` is True, this is done in-place; if `inPlace` is False, this returns a modified deepcopy. :: >>> from music21 import note >>> from music21 import stream :: >>> a = stream.Stream() >>> a.insert(20, note.Note()) >>> len(a) 1 :: >>> a.lowestOffset 20.0 :: >>> b = a.makeRests() >>> len(b) 2 :: >>> b.lowestOffset 0.0 OMIT_FROM_DOCS TODO: if inPlace is True, this should return None TODO: default inPlace = False ''' from music21 import stream if not inPlace: # make a copy returnObj = copy.deepcopy(s) else: returnObj = s #environLocal.printDebug([ # 'makeRests(): object lowestOffset, highestTime', oLow, oHigh]) if refStreamOrTimeRange is None: # use local oLowTarget = 0 if timeRangeFromBarDuration and returnObj.isMeasure: # NOTE: this will raise an exception if no meter can be found oHighTarget = returnObj.barDuration.quarterLength else: oHighTarget = returnObj.highestTime elif isinstance(refStreamOrTimeRange, stream.Stream): oLowTarget = refStreamOrTimeRange.lowestOffset oHighTarget = refStreamOrTimeRange.highestTime #environLocal.printDebug([ # 'refStream used in makeRests', oLowTarget, oHighTarget, # len(refStreamOrTimeRange)]) # treat as a list elif common.isListLike(refStreamOrTimeRange): oLowTarget = min(refStreamOrTimeRange) oHighTarget = max(refStreamOrTimeRange) #environLocal.printDebug([ # 'offsets used in makeRests', oLowTarget, oHighTarget, # len(refStreamOrTimeRange)]) if returnObj.hasVoices(): bundle = returnObj.voices else: bundle = [returnObj] for v in bundle: v._elementsChanged() # required to get correct offset times oLow = v.lowestOffset oHigh = v.highestTime # create rest from start to end qLen = oLow - oLowTarget if qLen > 0: r = note.Rest() r.duration.quarterLength = qLen #environLocal.printDebug(['makeRests(): add rests', r, r.duration]) # place at oLowTarget to reach to oLow v._insertCore(oLowTarget, r) # create rest from end to highest qLen = oHighTarget - oHigh #environLocal.printDebug(['v', v, oHigh, oHighTarget, 'qLen', qLen]) if qLen > 0: r = note.Rest() r.duration.quarterLength = qLen # place at oHigh to reach to oHighTarget v._insertCore(oHigh, r) v._elementsChanged() # must update otherwise might add double r if fillGaps: gapStream = v.findGaps() if gapStream is not None: for e in gapStream: r = note.Rest() r.duration.quarterLength = e.duration.quarterLength v._insertCore(e.offset, r) v._elementsChanged() #environLocal.printDebug(['post makeRests show()', v]) # NOTE: this sorting has been found to be necessary, as otherwise # the resulting Stream is not sorted and does not get sorted in # preparing musicxml output if v.autoSort: v.sort() # with auto sort no longer necessary. #returnObj.elements = returnObj.sorted.elements #s.isSorted = False # changes elements # returnObj._elementsChanged() # if returnObj.autoSort: # returnObj.sort() return returnObj
def music21ModWSGIFeatureApplication(environ, start_response): ''' Music21 webapp to demonstrate processing of a zip file containing scores. Will be moved and integrated into __init__.py upon developing a standardized URL format as application that can perform variety of commands on user-uploaded files ''' status = '200 OK' pathInfo = environ[ 'PATH_INFO'] # Contents of path after mount point of wsgi app but before question mark if pathInfo == '/uploadForm': output = getUploadForm() response_headers = [('Content-type', 'text/html'), ('Content-Length', str(len(output)))] start_response(status, response_headers) return [output] #command = pathInfo formFields = cgi.FieldStorage(fp=environ['wsgi.input'], environ=environ) # Check if form data is present. If not found, display error try: unused_subUploadFormFile = formFields['subUploadForm'] except: html = """ <html > <body style='font-family:calibri' bgcolor='#EEE' onLoad="toggleExtractors('m21')"> <table border=0 width='100%'> <tr><td align='center'> <table border=0 width='500px' cellpadding='10px' style='background-color:#FFF'> <tr><td align='left'> <h1>Error:</h1> <p>Form information not found</p> <p><a href='/music21/featureapp/uploadForm'>Try Again</a></p> </td></tr></table> </td></tr></table> </body></html> """ response_headers = [('Content-type', 'text/html'), ('Content-Length', str(len(html)))] start_response(status, response_headers) return [html] # Get file from POST uploadedFile = formFields['fileupload'].file filename = formFields['fileupload'].filename uploadType = formFields['fileupload'].type # Check if filename is empty - display no file chosen error if filename == "": html = """ <html > <body style='font-family:calibri' bgcolor='#EEE' onLoad="toggleExtractors('m21')"> <table border=0 width='100%'> <tr><td align='center'> <table border=0 width='500px' cellpadding='10px' style='background-color:#FFF'> <tr><td align='left'> <h1>Music 21 Feature Extraction:</h1> <p><b>Error:</b> No file selected</p> <p><a href='/music21/featureapp/uploadForm'>Try Again</a></p> </td></tr></table> </td></tr></table> </body></html> """ response_headers = [('Content-type', 'text/html'), ('Content-Length', str(len(html)))] start_response(status, response_headers) return [html] # Check if uploadType is zip - display no file chosen error if uploadType != "application/zip": html = """ <html > <body style='font-family:calibri' bgcolor='#EEE' onLoad="toggleExtractors('m21')"> <table border=0 width='100%'> <tr><td align='center'> <table border=0 width='500px' cellpadding='10px' style='background-color:#FFF'> <tr><td align='left'> <h1>Music 21 Feature Extraction:</h1> <p><b>Error:</b> File not in .zip format</p> <p><a href='/music21/featureapp/uploadForm'>Try Again</a></p> </td></tr></table> </td></tr></table> </body></html> """ response_headers = [('Content-type', 'text/html'), ('Content-Length', str(len(html)))] start_response(status, response_headers) return [html] # Setup Feature Extractors and Data Set ds = features.DataSet(classLabel='Class') featureIDList = list() # Check if features have been selected. Else display error try: unused_featureFile = formFields['features'] except: html = """ <html ><body> <h1>Error:</h1> <p>No extractors selected</p> <p><a href='/music21/featureapp/uploadForm'>try again</a></p> </body></html> """ return html if common.isListLike(formFields['features']): print(formFields['features']) for featureId in formFields['features']: featureIDList.append(str(featureId.value)) else: featureIDList.append(formFields['features'].value) fes = features.extractorsById(featureIDList) ds.addFeatureExtractors(fes) # Create ZipFile Object zipf = zipfile.ZipFile(uploadedFile, 'r') # Loop Through Files for scoreFileInfo in zipf.infolist(): filePath = scoreFileInfo.filename # Skip Directories if (filePath.endswith('/')): continue scoreFile = zipf.open(filePath) # Use Music21's converter to parse file parsedFile = idAndParseFile(scoreFile, filePath) # If valid music21 format, add to data set if parsedFile is not None: # Split into directory structure and filname pathPartitioned = filePath.rpartition('/') directory = pathPartitioned[0] filename = pathPartitioned[2] if directory == "": directory = 'uncategorized' ds.addData(parsedFile, classValue=directory, id=filename) # Process data set ds.process() # Get output format from POST and set appropriate output: outputFormatID = formFields['outputformat'].value if outputFormatID == CSV_OUTPUT_ID: output = features.OutputCSV(ds).getString() elif outputFormatID == ORANGE_OUTPUT_ID: output = features.OutputTabOrange(ds).getString() elif outputFormatID == ARFF_OUTPUT_ID: output = features.OutputARFF(ds).getString() else: output = "invalid output format" response_headers = [('Content-type', 'text/plain'), ('Content-Length', str(len(output)))] start_response(status, response_headers) return [output]
def getRealized( self, useDynamicContext=True, useVelocity=True, useArticulations=True, baseLevel=0.5, clip=True, ): ''' Get a realized unit-interval scalar for this Volume. This scalar is to be applied to the dynamic range of whatever output is available, whatever that may be. The `baseLevel` value is a middle value between 0 and 1 that all scalars modify. This also becomes the default value for unspecified dynamics. When scalars (between 0 and 1) are used, their values are doubled, such that mid-values (around .5, which become 1) make no change. This can optionally take into account `dynamicContext`, `useVelocity`, and `useArticulation`. If `useDynamicContext` is True, a context search for a dynamic will be done, else dynamics are ignored. Alternatively, the useDynamicContext may supply a Dynamic object that will be used instead of a context search. If `useArticulations` is True and parent is not None, any articulations found on that parent will be used to adjust the volume. Alternatively, the `useArticulations` parameter may supply a list of articulations that will be used instead of that available on a parent. The `velocityIsRelative` tag determines if the velocity value includes contextual values, such as dynamics and and accents, or not. :: >>> s = stream.Stream() >>> s.repeatAppend(note.Note('d3', quarterLength=.5), 8) >>> s.insert([0, dynamics.Dynamic('p'), 1, dynamics.Dynamic('mp'), 2, dynamics.Dynamic('mf'), 3, dynamics.Dynamic('f')]) :: >>> s.notes[0].volume.getRealized() 0.496... :: >>> s.notes[1].volume.getRealized() 0.496... :: >>> s.notes[2].volume.getRealized() 0.63779... :: >>> s.notes[7].volume.getRealized() 0.99212... :: >>> # velocity, if set, will be scaled by dynamics >>> s.notes[7].volume.velocity = 20 >>> s.notes[7].volume.getRealized() 0.22047... :: >>> # unless we set the velocity to not be relative >>> s.notes[7].volume.velocityIsRelative = False >>> s.notes[7].volume.getRealized() 0.1574803... ''' #velocityIsRelative might be best set at import. e.g., from MIDI, # velocityIsRelative is False, but in other applications, it may not # be val = baseLevel dm = None # no dynamic mark # velocity is checked first; the range between 0 and 1 is doubled, # to 0 to 2. a velocityScalar of .7 thus scales the base value of # .5 by 1.4 to become .7 if useVelocity: if self._velocity is not None: if not self.velocityIsRelative: # if velocity is not relateive # it should fully determines output independent of anything # else val = self.velocityScalar else: val = val * (self.velocityScalar * 2.0) # this value provides a good default velocity, as .5 is low # this not a scalar application but a shift. else: # target :0.70866 val += 0.20866 # only change the val from here if velocity is relative if self.velocityIsRelative: if useDynamicContext is not False: if hasattr( useDynamicContext, 'classes') and 'Dynamic' in useDynamicContext.classes: dm = useDynamicContext # it is a dynamic elif self.parent is not None: dm = self.getDynamicContext() # dm may be None else: environLocal.printDebug([ 'getRealized():', 'useDynamicContext is True but no dynamic supplied or found in context' ]) if dm is not None: # double scalare (so range is between 0 and 1) and scale # t he current val (around the base) val = val * (dm.volumeScalar * 2.0) # userArticulations can be a list of 1 or more articulation objects # as well as True/False if useArticulations is not False: am = None if common.isListLike(useArticulations): am = useArticulations elif hasattr(useArticulations, 'classes' ) and 'Articulation' in useArticulations.classes: am = [useArticulations] # place in a list elif self.parent is not None: am = self.parent.articulations if am is not None: for a in am: # add in volume shift for all articulations val += a.volumeShift if clip: # limit between 0 and 1 if val > 1: val = 1.0 elif val < 0: val = 0.0 # might to rebalance range after scalings # always update cached result each time this is called self._cachedRealized = val return val
def _parseData(self): ''' Parses data specified as strings in self.dataDict into objects in self.parsedDataDict ''' for (name, dataDictElement) in self.dataDict.iteritems(): if 'data' not in dataDictElement: self.addError("no data specified for data element " + unicode(dataDictElement)) continue dataStr = dataDictElement['data'] if 'fmt' in dataDictElement: fmt = dataDictElement['fmt'] if name in self.parsedDataDict: self.addError("duplicate definition for data named " + str(name) + " " + str(dataDictElement)) continue if fmt not in availableDataFormats: self.addError("invalid data format for data element " + str(dataDictElement)) continue if fmt == 'string' or fmt == 'str': if dataStr.count("'") == 2: # Single Quoted String data = dataStr.replace("'", "") # remove excess quotes elif dataStr.count("\"") == 2: # Double Quoted String data = dataStr.replace("\"", "") # remove excess quotes else: self.addError( "invalid string (not in quotes...) for data element " + str(dataDictElement)) continue elif fmt == 'int': try: data = int(dataStr) except: self.addError("invalid integer for data element " + str(dataDictElement)) continue elif fmt in ['bool', 'boolean']: if dataStr in ['true', 'True']: data = True elif dataStr in ['false', 'False']: data = False else: self.addError("invalid boolean for data element " + str(dataDictElement)) continue elif fmt == 'list': # in this case dataStr should actually be an list object. if not common.isListLike(dataStr): self.addError( "list format must actually be a list structure " + str(dataDictElement)) continue data = [] for elementStr in dataStr: if common.isStr(elementStr): (matchFound, dataElement ) = self.parseStringToPrimitive(elementStr) if not matchFound: self.addError( "format could not be detected for data element " + str(elementStr)) continue else: dataElement = elementStr data.append(dataElement) else: if fmt in ['xml', 'musicxml']: if dataStr.find("<!DOCTYPE") == -1: dataStr = """<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 1.1 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">""" + dataStr if dataStr.find("<?xml") == -1: dataStr = """<?xml version="1.0" encoding="UTF-8"?>""" + dataStr try: data = converter.parseData(dataStr) except converter.ConverterException as e: #self.addError("Error parsing data variable "+name+": "+str(e)+"\n\n"+dataStr) self.addError( "Error parsing data variable " + name + ": " + unicode(e) + "\n\n" + dataStr, e) continue else: # No format specified (matchFound, data) = self.parseStringToPrimitive(dataStr) if not matchFound: self.addError( "format could not be detected for data element " + str(dataDictElement)) continue self.parsedDataDict[name] = data
def getColor(color): ''' Convert any specification of a color to a hexadecimal color used by matplotlib. >>> graph.utilities.getColor('red') '#ff0000' >>> graph.utilities.getColor('r') '#ff0000' >>> graph.utilities.getColor('Steel Blue') '#4682b4' >>> graph.utilities.getColor('#f50') '#ff5500' >>> graph.utilities.getColor([0.5, 0.5, 0.5]) '#808080' >>> graph.utilities.getColor(0.8) '#cccccc' >>> graph.utilities.getColor([0.8]) '#cccccc' >>> graph.utilities.getColor([255, 255, 255]) '#ffffff' Invalid colors raise GraphExceptions: >>> graph.utilities.getColor('l') Traceback (most recent call last): music21.graph.utilities.GraphException: invalid color abbreviation: l >>> graph.utilities.getColor('chalkywhitebutsortofgreenish') Traceback (most recent call last): music21.graph.utilities.GraphException: invalid color name: chalkywhitebutsortofgreenish >>> graph.utilities.getColor(True) Traceback (most recent call last): music21.graph.utilities.GraphException: invalid color specification: True ''' # expand a single value to three if common.isNum(color): color = [color, color, color] if isinstance(color, str): if color[0] == '#': # assume is hex # this will expand three-value codes, and check for badly # formed codes return webcolors.normalize_hex(color) color = color.lower().replace(' ', '') # check for one character matplotlib colors if len(color) == 1: colorMap = { 'b': 'blue', 'g': 'green', 'r': 'red', 'c': 'cyan', 'm': 'magenta', 'y': 'yellow', 'k': 'black', 'w': 'white' } try: color = colorMap[color] except KeyError: raise GraphException('invalid color abbreviation: %s' % color) try: return webcolors.css3_names_to_hex[color] except KeyError: # no color match raise GraphException('invalid color name: %s' % color) elif common.isListLike(color): percent = False for sub in color: if sub < 1: percent = True break if percent: if len(color) == 1: color = [color[0], color[0], color[0]] # convert to 0 100% values as strings with % symbol colorStrList = [str(x * 100) + "%" for x in color] return webcolors.rgb_percent_to_hex(colorStrList) else: # assume integers return webcolors.rgb_to_hex(tuple(color)) raise GraphException('invalid color specification: %s' % color)
def __init__(self, groupFilterList=()): super().__init__() if not common.isListLike(groupFilterList): groupFilterList = [groupFilterList] self.groupFilterList = groupFilterList
def getPlotsToMake(*args, **keywords): ''' Given `format` and `values` provided as arguments or keywords, return a list of plot classes. no arguments = horizontalbar >>> graph.getPlotsToMake() [<class 'music21.graph.plot.HorizontalBarPitchSpaceOffset'>] >>> graph.getPlotsToMake('windowed') [<class 'music21.graph.plot.WindowedKey'>] ''' plotClasses = [ # histograms plot.HistogramPitchSpace, plot.HistogramPitchClass, plot.HistogramQuarterLength, # scatters plot.ScatterPitchSpaceQuarterLength, plot.ScatterPitchClassQuarterLength, plot.ScatterPitchClassOffset, plot.ScatterPitchSpaceDynamicSymbol, # offset based horizontal plot.HorizontalBarPitchSpaceOffset, plot.HorizontalBarPitchClassOffset, # weighted scatter plot.ScatterWeightedPitchSpaceQuarterLength, plot.ScatterWeightedPitchClassQuarterLength, plot.ScatterWeightedPitchSpaceDynamicSymbol, # 3d graphs plot.Plot3DBarsPitchSpaceQuarterLength, # windowed plots plot.WindowedKey, plot.WindowedAmbitus, # instrumentation and part graphs plot.Dolan, ] showFormat = '' values = () # can match by format if 'format' in keywords: showFormat = keywords['format'] elif 'showFormat' in keywords: showFormat = keywords['showFormat'] if 'values' in keywords: values = keywords['values'] # should be a tuple or list #environLocal.printDebug(['got args pre conversion', args]) # if no args, use pianoroll foundClassName = None if (not args and showFormat == '' and not values): showFormat = 'horizontalbar' values = 'pitch' elif len(args) == 1: formatCandidate = utilities.userFormatsToFormat(args[0]) #environLocal.printDebug(['formatCandidate', formatCandidate]) match = False if formatCandidate in utilities.FORMATS: showFormat = formatCandidate values = 'pitch' match = True # if one arg, assume it is a histogram value if formatCandidate in utilities.VALUES: showFormat = 'histogram' values = (args[0],) match = True # permit directly matching the class name if not match: for className in plotClasses: if formatCandidate in str(className).lower(): match = True foundClassName = className break elif len(args) > 1: showFormat = utilities.userFormatsToFormat(args[0]) values = args[1:] # get all remaining if not common.isListLike(values): values = (values,) # make it mutable values = list(values) #environLocal.printDebug(['got args post conversion', 'format', showFormat, # 'values', values, 'foundClassName', foundClassName]) # clean data and process synonyms # will return unaltered if no change showFormat = utilities.userFormatsToFormat(showFormat) values = utilities.userValuesToValues(values) #environLocal.printDebug(['plotStream: format, values', showFormat , values]) plotMake = [] if showFormat.lower() == 'all': plotMake = plotClasses elif foundClassName is not None: plotMake = [foundClassName] # place in a list else: plotMakeCandidates = [] # store pairs of score, class from music21 import stream sDummy = stream.Stream() for plotClassName in plotClasses: # try to match by complete class name pcnStr = plotClassName.__name__.lower() if pcnStr == showFormat.lower(): #environLocal.printDebug(['got plot class:', plotClassName]) plotMake.append(plotClassName) elif pcnStr.replace('plot', '') == showFormat.lower(): plotMake.append(plotClassName) elif pcnStr in utilities.PLOTCLASS_SYNONYMS: syns = utilities.PLOTCLASS_SYNONYMS[pcnStr] for syn in syns: if syn == showFormat.lower(): plotMake.append(plotClassName) # try direct match of format and values plotClassNameValues = [x.axisLabelDefault.lower().replace(' ', '') for x in plotClassName(sDummy).allAxes if x is not None] plotClassNameFormat = plotClassName.graphType.lower() if showFormat and plotClassNameFormat != showFormat.lower(): continue #environLocal.printDebug(['matching format', showFormat]) # see if a matching set of values is specified # normally plots need to match all values match = [] for requestedValue in values: if requestedValue is None: continue if (requestedValue.lower() in plotClassNameValues and requestedValue not in match): # do not allow the same value to be requested match.append(requestedValue) if pcnStr in utilities.PLOTCLASS_SYNONYMS: syns = utilities.PLOTCLASS_SYNONYMS[pcnStr] if requestedValue in syns: match.append(requestedValue) if len(match) >= len(values): plotMake.append(plotClassName) else: sortTuple = (len(match), plotClassName) plotMakeCandidates.append(sortTuple) # if no matches, try something more drastic: if not plotMake and plotMakeCandidates: plotMakeCandidates.sort(key=lambda x: (x[0], x[1].__name__.lower()) ) # last in list has highest score; second item is class plotMake.append(plotMakeCandidates[-1][1]) elif not plotMake: # none to make and no candidates for plotClassName in plotClasses: # create a list of all possible identifiers plotClassIdentifiers = [plotClassName.graphType.lower()] plotClassIdentifiers += [x.axisLabelDefault.lower().replace(' ', '') for x in plotClassName(sDummy).allAxes if x is not None] # combine format and values args for requestedValue in [showFormat] + values: if requestedValue.lower() in plotClassIdentifiers: plotMake.append(plotClassName) break if plotMake: # found a match break #environLocal.printDebug(['plotMake', plotMake]) return plotMake