def _stripError(self, value): r''' Strip error symbols from a numerical value. Return cleaned source and sym. Only one error symbol is expected per string. >>> d = metadata.Date() >>> d._stripError('1247~') ('1247', 'approximate') >>> d._stripError('234.43?') ('234.43', 'uncertain') >>> d._stripError('234.43') ('234.43', None) ''' if common.isNum(value): # if a number, let pass return value, None else: dateStr = value sym = self.approximateSymbols + self.uncertainSymbols found = None for char in dateStr: if char in sym: found = char break if found is None: return dateStr, None elif found in self.approximateSymbols: dateStr = dateStr.replace(found, '') return dateStr, 'approximate' elif found in self.uncertainSymbols: dateStr = dateStr.replace(found, '') return dateStr, 'uncertain'
def _filterNodeId(self, id): '''Given a node id, return the edge coordinates. Node 1 is the first node, even though the edge coordinates are 'start' and 0. >>> edgeList = ['M2', 'M2', 'm2', 'M2', 'M2', 'M2', 'm2'] >>> net = IntervalNetwork() >>> net.setEdges(edgeList) >>> net._filterNodeId(1) ('start', 0) >>> net._filterNodeId([3,4]) (3, 4) >>> net._filterNodeId('last') (5, 'end') >>> net._filterNodeId('first') ('start', 0) ''' if common.isNum(id): # assume counting nodes from 1 return self._nodesOrdered[id-1 % len(self._nodesOrdered)] if common.isStr(id): if id.lower() in ['start', 'first']: return self._getFirstNode() elif id.lower() in ['end', 'last']: return self._getLastNode() else: # match coords if tuple(id) in self._nodesOrdered: return tuple(id)
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 _stripError(self, value): '''Strip error symbols from a numerical value. Return cleaned source and sym. Only one error symbol is expected per string. >>> d = Date() >>> d._stripError('1247~') ('1247', 'approximate') >>> d._stripError('234.43?') ('234.43', 'uncertain') >>> d._stripError('234.43') ('234.43', None) ''' if common.isNum(value): # if a number, let pass return value, None else: str = value sym = APPROXIMATE + UNCERTAIN found = None for char in str: if char in sym: found = char break if found == None: return str, None elif found in APPROXIMATE: str = str.replace(found, '') return str, 'approximate' elif found in UNCERTAIN: str = str.replace(found, '') return str, 'uncertain'
def velocity(self, value): if not common.isNum(value): raise VolumeException('value provided for velocity must be a number, not %s' % value) if value < 0: self._velocityScalar = 0.0 elif value > 127: self._velocityScalar = 1.0 else: self._velocityScalar = value / 127.0
def _setVelocity(self, value): if not common.isNum(value): raise VolumeException('value provided for velocity must be a number, not %s' % value) if value < 0: self._velocity = 0 elif value > 127: self._velocity = 127 else: self._velocity = value
def velocityScalar(self, value): if not common.isNum(value): raise VolumeException('value provided for velocityScalar must be a number, not %s' % value) if value < 0: scalar = 0 elif value > 1: scalar = 1 else: scalar = value self._velocity = int(round(scalar * 127))
def __init__(self, text=None, number=1, syllabic=None): if not common.isStr(text): # do not want to do this unless we are sure this is not a string # possible might alter unicode or other string-like representations self.text = str(text) else: self.text = text if not common.isNum(number): raise LyricException('Number best be number') self.number = number self.syllabic = syllabic # can be begin, middle, or end
def printDebug(self, msg, statusLevel=common.DEBUG_USER): '''Format one or more data elements into string, and print it to stderr. The first arg can be a list of string; lists are concatenated with common.formatStr(). ''' if not common.isNum(statusLevel): raise EnvironmentException('bad statusLevel argument given: %s' % statusLevel) if self.__getitem__('debug') >= statusLevel: if common.isStr(msg): msg = [msg] # make into a list if msg[0] != self.modNameParent and self.modNameParent != None: msg = [self.modNameParent + ':'] + msg # pass list to common.formatStr msg = common.formatStr(*msg) sys.stderr.write(msg)
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 warn(self, msg, header=None): ''' To print a warning to the user, send a list of strings to this method. Similar to printDebug but even if debug is off. ''' if isinstance(msg, six.string_types): msg = [msg] # make into a list elif isinstance(msg, dict): msg = [repr(msg)] elif common.isNum(msg): msg = [str(msg)] if header is None: if msg[0] != self.modNameParent and self.modNameParent is not None: msg = [self.modNameParent + ': WARNING:'] + msg else: msg = [header] + msg msg = common.formatStr(*msg) sys.stderr.write(msg)
def _quickEnharmonicString(nameStr, direction='up', allowDoubleAccidentals=True): ''' Helper function for quickHigherEnharmonicString and quickLowerEnharmonicString ''' if direction == 'up': addNum = 4 elif direction == 'down': addNum = -4 else: raise Base40Exception("Not a valid direction, {}".format(direction)) enharmonics = [] if common.isNum(nameStr): base40num = nameStr nameStr = base40Equivalent.get(base40num, None) if nameStr is None: base40num = None else: base40num = base40Representation.get(nameStr, None) while base40num is not None: base40num = (base40num + addNum) % 40 if base40num == 0: base40num = 40 base40str = base40Equivalent.get(base40num, None) if allowDoubleAccidentals is False and base40str is not None and len(base40str) > 2: base40str = None if base40str is None: base40num = None else: if base40str[0] == nameStr[0]: base40num = None base40str = None for e in enharmonics: if base40str[0] == e[0]: base40num = None base40str = None if base40str is not None: enharmonics.append(base40str) return enharmonics
def _fixId(e): if (e.id is not None and common.isNum(e.id) and e.id > defaults.minIdNumberToConsiderMemoryLocation): e.id = id(e)
def process(self, minWindow=1, maxWindow=1, windowStepSize=1, windowType='overlap', includeTotalWindow=True): ''' Main method for windowed analysis across one or more window size. Calls :meth:`~music21.analysis.WindowedAnalysis._analyze` for the number of different window sizes to be analyzed. The `minWindow` and `maxWindow` set the range of window sizes in quarter lengths. The `windowStepSize` parameter determines the increment between these window sizes, in quarter lengths. If `minWindow` or `maxWindow` is None, the largest window size available will be set. If `includeTotalWindow` is True, the largest window size will always be added. >>> s = corpus.parse('bach/bwv324') >>> p = analysis.discrete.KrumhanslSchmuckler() >>> # placing one part into analysis >>> wa = analysis.windowed.WindowedAnalysis(s.parts[0], p) >>> x, y, z = wa.process(1, 1, includeTotalWindow=False) >>> len(x) # we only have one series of windows 1 >>> y[0][0].startswith('#') # for each window, we get a solution and a color True >>> x[0][0][0] <music21.pitch.Pitch B> >>> x, y, z = wa.process(1, 2, includeTotalWindow=False) >>> len(x) # we have two series of windows 2 >>> x[0][0] # the data returned is processor dependent; here we get (<music21.pitch.Pitch B>, 'major', 0.6868258874056411) >>> y[0][0].startswith('#') # a color is returned for each matching data position True ''' if maxWindow == None: maxLength = len(self._windowedStream) else: maxLength = maxWindow if minWindow == None: minLength = len(self._windowedStream) else: minLength = minWindow if windowType == None: windowType = 'overlap' elif windowType.lower() in ['overlap']: windowType = 'overlap' elif windowType.lower() in ['nooverlap', 'nonoverlapping']: windowType = 'noOverlap' elif windowType.lower() in ['adjacentaverage']: windowType = 'adjacentAverage' # need to create storage for the output of each row, or the processing # of all windows of a single size across the entire Stream solutionMatrix = [] colorMatrix = [] # store meta data about each row as a dictionary metaMatrix = [] if common.isNum(windowStepSize): windowSizes = list(range(minLength, maxLength+1, windowStepSize)) else: num, junk = common.getNumFromStr(windowStepSize) windowSizes = [] x = minLength while True: windowSizes.append(x) x = x * int(round(float(num))) if x > (maxLength * .75): break if includeTotalWindow: totalWindow = len(self._windowedStream) if totalWindow not in windowSizes: windowSizes.append(totalWindow) for i in windowSizes: #environLocal.printDebug(['processing window:', i]) # each of these results are lists, where len is based on soln, colorn = self._analyze(i, windowType=windowType) # store lists of results in a list of lists solutionMatrix.append(soln) colorMatrix.append(colorn) meta = {'windowSize': i} metaMatrix.append(meta) return solutionMatrix, colorMatrix, metaMatrix
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 process(self, minWindow=1, maxWindow=1, windowStepSize=1, windowType='overlap', includeTotalWindow=True): ''' Main method for windowed analysis across one or more window sizes. Calls :meth:`~music21.analysis.WindowedAnalysis.analyze` for the number of different window sizes to be analyzed. The `minWindow` and `maxWindow` set the range of window sizes in quarter lengths. The `windowStepSize` parameter determines the increment between these window sizes, in quarter lengths. If `minWindow` or `maxWindow` is None, the largest window size available will be set. If `includeTotalWindow` is True, the largest window size will always be added. >>> s = corpus.parse('bach/bwv324') >>> ksAnalyzer = analysis.discrete.KrumhanslSchmuckler() placing one part into analysis >>> sopr = s.parts[0] >>> wa = analysis.windowed.WindowedAnalysis(sopr, ksAnalyzer) >>> solutions, colors, meta = wa.process(1, 1, includeTotalWindow=False) >>> len(solutions) # we only have one series of windows 1 >>> solutions, colors, meta = wa.process(1, 2, includeTotalWindow=False) >>> len(solutions) # we have two series of windows 2 >>> solutions[1] [(<music21.pitch.Pitch B>, 'major', 0.6868...), (<music21.pitch.Pitch B>, 'minor', 0.8308...), (<music21.pitch.Pitch D>, 'major', 0.6868...), (<music21.pitch.Pitch B>, 'minor', 0.8308...),...] >>> colors[1] ['#ffb5ff', '#9b519b', '#ffd752', '#9b519b', ...] >>> meta [{'windowSize': 1}, {'windowSize': 2}] ''' if maxWindow is None: maxLength = len(self._windowedStream) else: maxLength = maxWindow if minWindow is None: minLength = len(self._windowedStream) else: minLength = minWindow if windowType is None: windowType = 'overlap' elif windowType.lower() in ['overlap']: windowType = 'overlap' elif windowType.lower() in ['nooverlap', 'nonoverlapping']: windowType = 'noOverlap' elif windowType.lower() in ['adjacentaverage']: windowType = 'adjacentAverage' # need to create storage for the output of each row, or the processing # of all windows of a single size across the entire Stream solutionMatrix = [] colorMatrix = [] # store meta data about each row as a dictionary metaMatrix = [] if common.isNum(windowStepSize): windowSizes = list(range(minLength, maxLength + 1, windowStepSize)) else: num, junk = common.getNumFromStr(windowStepSize) windowSizes = [] x = minLength while True: windowSizes.append(x) x = x * round(int(num)) if x > (maxLength * 0.75): break if includeTotalWindow: totalWindow = len(self._windowedStream) if totalWindow not in windowSizes: windowSizes.append(totalWindow) for i in windowSizes: #environLocal.printDebug(['processing window:', i]) # each of these results are lists, where len is based on soln, colorn = self.analyze(i, windowType=windowType) # store lists of results in a list of lists solutionMatrix.append(soln) colorMatrix.append(colorn) meta = {'windowSize': i} metaMatrix.append(meta) return solutionMatrix, colorMatrix, metaMatrix
def _setVolumeScalar(self, value): # we can manually set this to be anything, overriding defaults if common.isNum(value) and 0 <= value <= 1: self._volumeScalar = value else: raise DynamicException('cannot set as volume scalar to: %s', value)
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 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 process(self, minWindow=1, maxWindow=1, windowStepSize=1, windowType='overlap', includeTotalWindow=True): ''' Main method for windowed analysis across one or more window sizes. Calls :meth:`~music21.analysis.WindowedAnalysis.analyze` for the number of different window sizes to be analyzed. The `minWindow` and `maxWindow` set the range of window sizes in quarter lengths. The `windowStepSize` parameter determines the increment between these window sizes, in quarter lengths. If `minWindow` or `maxWindow` is None, the largest window size available will be set. If `includeTotalWindow` is True, the largest window size will always be added. >>> s = corpus.parse('bach/bwv324') >>> ksAnalyzer = analysis.discrete.KrumhanslSchmuckler() placing one part into analysis >>> sopr = s.parts[0] >>> wa = analysis.windowed.WindowedAnalysis(sopr, ksAnalyzer) >>> solutions, colors, meta = wa.process(1, 1, includeTotalWindow=False) >>> len(solutions) # we only have one series of windows 1 >>> solutions, colors, meta = wa.process(1, 2, includeTotalWindow=False) >>> len(solutions) # we have two series of windows 2 >>> solutions[1] [(<music21.pitch.Pitch B>, 'major', 0.6868...), (<music21.pitch.Pitch B>, 'minor', 0.8308...), (<music21.pitch.Pitch D>, 'major', 0.6868...), (<music21.pitch.Pitch B>, 'minor', 0.8308...),...] >>> colors[1] ['#ffb5ff', '#9b519b', '#ffd752', '#9b519b', ...] >>> meta [{'windowSize': 1}, {'windowSize': 2}] ''' if maxWindow is None: maxLength = len(self._windowedStream) else: maxLength = maxWindow if minWindow is None: minLength = len(self._windowedStream) else: minLength = minWindow if windowType is None: windowType = 'overlap' elif windowType.lower() in ['overlap']: windowType = 'overlap' elif windowType.lower() in ['nooverlap', 'nonoverlapping']: windowType = 'noOverlap' elif windowType.lower() in ['adjacentaverage']: windowType = 'adjacentAverage' # need to create storage for the output of each row, or the processing # of all windows of a single size across the entire Stream solutionMatrix = [] colorMatrix = [] # store meta data about each row as a dictionary metaMatrix = [] if common.isNum(windowStepSize): windowSizes = list(range(minLength, maxLength + 1, windowStepSize)) else: num, junk = common.getNumFromStr(windowStepSize) windowSizes = [] x = minLength while True: windowSizes.append(x) x = x * round(int(num)) if x > (maxLength * 0.75): break if includeTotalWindow: totalWindow = len(self._windowedStream) if totalWindow not in windowSizes: windowSizes.append(totalWindow) for i in windowSizes: # environLocal.printDebug(['processing window:', i]) # each of these results are lists, where len is based on solution, colorName = self.analyze(i, windowType=windowType) # store lists of results in a list of lists solutionMatrix.append(solution) colorMatrix.append(colorName) meta = {'windowSize': i} metaMatrix.append(meta) return solutionMatrix, colorMatrix, metaMatrix
def _fixId(innerEl): if (innerEl.id is not None and common.isNum(innerEl.id) and innerEl.id > defaults.minIdNumberToConsiderMemoryLocation): innerEl.id = id(innerEl)
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)