def psSetTransposer(chord, trans): """transposes an entire set by trans, no mod12 retains oct info, micro info >>> pcSetTransposer([3,4,5], 14) (5, 6, 7) """ newSet = [] for pc in chord: ## works for negative or positive numbers newSet.append(pitchTools.psTransposer(pc, trans)) return tuple(newSet)
def _scoreMain(self): """creates score >>> from athenaCL.libATH.libTM import texture >>> ti = texture.factory('lg') >>> ti.tmName == 'LineGroove' True >>> ti.loadDefault() >>> ti.score() == True True """ # texture-wide time elements inst = self.getInst() tStart, tEnd = self.getTimeRange() tCurrent = tStart # texture-wide (self.textQ amd)options # used for optional parallel voices textParallelVoiceList = self.getTextStatic("pml", "transpositionList") textParallelDelayTime = self.getTextStatic("pml", "timeDelay") # get field, octave selection method value textFieldLevel = self.getTextStatic("lfm", "level") textOctaveLevel = self.getTextStatic("lom", "level") textPitchSelectorControl = self.getTextStatic("psc", "selectionString") # create a list of chords from the appropriate pitch mode for pathPos in self.getPathPos(): chordCurrent = self.getPitchGroup(pathPos) multisetCurrent = self.getMultiset(pathPos) tStartSet, tEndSet = self.clockPoints() selectorChordPos = basePmtr.Selector(range(len(chordCurrent)), textPitchSelectorControl) tStartSetReal = copy.deepcopy(tCurrent) self.stateUpdate(tCurrent, chordCurrent, None, multisetCurrent, None, None) if textFieldLevel == "set": transCurrent = self.getField(tCurrent) # choose PITCHFIELD if textOctaveLevel == "set": octCurrent = self.getOct(tCurrent) # choose OCTAVE while 1: # pitch in chord if tCurrent >= tEndSet: break # choose pc from chord ps = chordCurrent[selectorChordPos()] # get position w/n chord self.stateUpdate(tCurrent, chordCurrent, ps, multisetCurrent, None, None) if textFieldLevel == "event": transCurrent = self.getField(tCurrent) # choose PITCHFIELD if textOctaveLevel == "event": octCurrent = self.getOct(tCurrent) # choose OCTAVE psReal = pitchTools.psToTempered(ps, octCurrent, self.temperamentObj, transCurrent) self.stateUpdate(tCurrent, chordCurrent, ps, multisetCurrent, None, psReal) bpm, pulse, dur, sus, acc = self.getRhythm(tCurrent) if acc == 0 and not self.silenceMode: # this is a rest tCurrent = tCurrent + dur continue amp = self.getAmp(tCurrent) * acc # choose amp, pan pan = self.getPan(tCurrent) auxiliary = self.getAux(tCurrent) # chooose AUX, pack into list eventDict = self.makeEvent(tCurrent, bpm, pulse, dur, sus, acc, amp, psReal, pan, auxiliary) self.storeEvent(eventDict) # Parallel transposition offset = 0 for parallelVoice in textParallelVoiceList: # offset to avoid amp problems, correct error w/ offset tCurrent = tCurrent + textParallelDelayTime offset = offset + textParallelDelayTime psText = pitchTools.psTransposer(psReal, parallelVoice) eventDict = self.makeEvent(tCurrent, bpm, pulse, dur, sus, acc, amp, psText, pan, auxiliary) self.storeEvent(eventDict) # move clocks forward by dur unit tCurrent = (tCurrent + dur) - offset self.clockForward() # advances path positon return 1
def _scoreMain(self): """creates score >>> from athenaCL.libATH.libTM import texture >>> ti = texture.factory('lg') >>> ti.tmName == 'LineGroove' True >>> ti.loadDefault() >>> ti.score() == True True """ # texture-wide time elements inst = self.getInst() tStart, tEnd = self.getTimeRange() tCurrent = tStart #texture-wide (self.textQ amd)options #used for optional parallel voices textParallelVoiceList = self.getTextStatic('pml', 'transpositionList') textParallelDelayTime = self.getTextStatic('pml', 'timeDelay') # get field, octave selection method value textFieldLevel = self.getTextStatic('lfm', 'level') textOctaveLevel = self.getTextStatic('lom', 'level') textPitchSelectorControl = self.getTextStatic('psc', 'selectionString') # create a list of chords from the appropriate pitch mode for pathPos in self.getPathPos(): chordCurrent = self.getPitchGroup(pathPos) multisetCurrent = self.getMultiset(pathPos) tStartSet, tEndSet = self.clockPoints() selectorChordPos = basePmtr.Selector(list(range(len(chordCurrent))), textPitchSelectorControl) tStartSetReal = copy.deepcopy(tCurrent) self.stateUpdate(tCurrent, chordCurrent, None, multisetCurrent, None, None) if textFieldLevel == 'set': transCurrent = self.getField(tCurrent) # choose PITCHFIELD if textOctaveLevel == 'set': octCurrent = self.getOct(tCurrent) # choose OCTAVE while 1: # pitch in chord if tCurrent >= tEndSet: break # choose pc from chord ps = chordCurrent[selectorChordPos()] # get position w/n chord self.stateUpdate(tCurrent, chordCurrent, ps, multisetCurrent, None, None) if textFieldLevel == 'event': transCurrent = self.getField(tCurrent) # choose PITCHFIELD if textOctaveLevel == 'event': octCurrent = self.getOct(tCurrent) # choose OCTAVE psReal = pitchTools.psToTempered(ps, octCurrent, self.temperamentObj, transCurrent) self.stateUpdate(tCurrent, chordCurrent, ps, multisetCurrent, None, psReal) bpm, pulse, dur, sus, acc = self.getRhythm(tCurrent) if acc == 0 and not self.silenceMode: # this is a rest tCurrent = tCurrent + dur continue amp = self.getAmp(tCurrent) * acc # choose amp, pan pan = self.getPan(tCurrent) auxiliary = self.getAux(tCurrent) # chooose AUX, pack into list eventDict = self.makeEvent(tCurrent, bpm, pulse, dur, sus, acc, amp, psReal, pan, auxiliary) self.storeEvent(eventDict) # Parallel transposition offset = 0 for parallelVoice in textParallelVoiceList: #offset to avoid amp problems, correct error w/ offset tCurrent = tCurrent + textParallelDelayTime offset = offset + textParallelDelayTime psText = pitchTools.psTransposer(psReal, parallelVoice) eventDict = self.makeEvent(tCurrent, bpm, pulse, dur, sus, acc, amp, psText, pan, auxiliary) self.storeEvent(eventDict) # move clocks forward by dur unit tCurrent = (tCurrent + dur) - offset self.clockForward() # advances path positon return 1
def psScale(self, pitchFormat, contourForm, psBase, microTone=.5): """translates a scale form in notation [0,-1,0,1] into various pitch representations. integers in the scale form are interpreted in three ways: as chromatic 1/2 steps, as diatonic scale pitches (either from the local set or the entire path), or as units of some microtonal size this returns a list representing the scale steps in relation to psBase psBase needs to be found in terms of the path, which may not consist only of ints returns a psCountourReference, which is alway tempered pitch values """ # not sure this needs to be an int #assert drawer.isInt(psBase) octCurrent = self.pmtrObjDict['octQ'].currentValue transCurrent = self.pmtrObjDict['fieldQ'].currentValue #currentChord = self.stateCurrentChord # not needed for all forms, but myst always get if pitchFormat == 'set': pitchGroup = self.refDict['stateCurrentChord'] elif pitchFormat == 'path': pitchGroup = self.refDict['statePathList'] else: # non given, but note used pitchGroup = self.refDict['stateCurrentChord'] refScales, lowerUpper = extractNeighbors(pitchGroup, psBase) pcContourDict = mapNeighbors(refScales, psBase, contourForm) #print pcContourDict # get pitch scale # this has the mapping with the appropriate pitches # N.B: danger here of getting mistransposed values # previously was an error and corrected in _splitPch psContourRef = [] if pitchFormat == 'chromatic': for entry in contourForm: # transpose before getting temperament pcSpace = pitchTools.psTransposer(psBase, entry) psReal = pitchTools.psToTempered(pcSpace, octCurrent, self.temperamentObj, transCurrent) psContourRef.append(psReal) # transpose by half steps # sets: derive scale from set elif pitchFormat == 'set' or pitchFormat == 'path': for entry in contourForm: pcSpace = pcContourDict[ entry] # scale step is a key, gets pcSpace psReal = pitchTools.psToTempered(pcSpace, octCurrent, self.temperamentObj, transCurrent) psContourRef.append(psReal) # transpose by half steps elif pitchFormat == 'microtone': # microtonal for entry in contourForm: # treat scale step as microtone scaler # must do transposition after converting to PCH if entry * microTone > entry * 2: environment.printWarn([ lang.WARN, 'microtone large (%s)' % (entry * microTone) ]) trans = (transCurrent + (entry * microTone)) psReal = pitchTools.psToTempered(psBase, octCurrent, self.temperamentObj, trans) psContourRef.append(psReal) # transpose by half steps else: raise ValueError('no such pitchFormat') # this now returns psReals, not pch values return psContourRef, refScales
def extractNeighbors(pitchGroup, baseNote, scales=None): """takes a set, or a whole path, and derives a pc scale, a pitch space scale, and provides the upper and lower note to baseNote baseNote should be represented in the pitch group we need to know our current reference position in the pitchGroup pitchGroup has pitch pre-temperament; thus baseNote should be pre-temperament may be a psReal pitchGroup: from a refDict, containing stateCurrentChord, or statePathList will be a list of raw psReal values: could be floats, and could have micro specification """ # if scales given and no pitchGroup is given if scales != None and pitchGroup == None: colPitchSpace = scales[0] colPitchClass = scales[1] else: # given a set or path as pitchGroup if drawer.isNum(pitchGroup[0]): pitchGroup = [ pitchGroup, ] # make all look like paths colPitchSpace = [] colPitchClass = [] for set in pitchGroup: for entry in set: if entry not in colPitchSpace: colPitchSpace.append(entry) # round; zero trans gets mod12 entryPC = pitchTools.pcTransposer(entry, 0) if entryPC not in colPitchClass: colPitchClass.append(entryPC) colPitchSpace.sort() colPitchClass.sort() scales = colPitchSpace, colPitchClass # use pitch class space to get neighbors # can use pitch space in the future? wrap around is strange octaveAdjust = 0 baseOctMult, basePC = pitchTools.splitOctPs(baseNote) # although baseNote may be a float (already tempered) # basePC seems to need to be an int, as it is used to find # a position in the scale; for this reason it seems like # the path positions value, and not the tempered pitch # should be coming in as the baseNote: tmerperament could cause # a rounding error and pass a pitch that is not in the scale at all #print _MOD, basePC, colPitchClass idx = None try: idx = colPitchClass.index(basePC) except ValueError: # not in the collected pitches; try rounding for i in range(len(colPitchClass)): # compare rounded versions, as floats may not match if round(colPitchClass[i], 2) == round(basePC, 2): idx = i # print _MOD, 'found rounded match' if idx == None: idx = 0 environment.printDebug( 'no match between base pitch and collected pitches') idxL = idx - 1 # lower neighbor if idxL == -1: # wrap index around idxL = len(colPitchClass) - 1 octaveAdjust = -1 idxU = idx + 1 # upper neighbor if idxU == len(colPitchClass): idxU = 0 octaveAdjust = 1 neighborL = colPitchClass[idxL] if octaveAdjust == -1: neighborL = pitchTools.psTransposer(neighborL, -12) neighborU = colPitchClass[idxU] if octaveAdjust == 1: neighborU = pitchTools.psTransposer(neighborU, 12) # do octave adjust ment relative to baseNote in pitch space neighborL = pitchTools.psTransposer(neighborL, (12 * baseOctMult)) neighborU = pitchTools.psTransposer(neighborU, (12 * baseOctMult)) lowerUpper = neighborL, neighborU return scales, lowerUpper
def _scoreMain(self): """creates score >>> from athenaCL.libATH.libTM import texture >>> ti = texture.factory('LineCluster') >>> ti.tmName == 'LineCluster' True >>> ti.loadDefault() >>> ti.score() == True True """ # texture-wide PATH/PITCH elements # texture-wide time elements inst = self.getInst() tStart, tEnd = self.getTimeRange() tCurrent = tStart # texture-wide TEXTURE (self.textQ amd)options #used for optional parallel voices textParallelVoiceList = self.getTextStatic('pml', 'transpositionList') textParallelDelayTime = self.getTextStatic('pml', 'timeDelay') # get field, octave selection method value textFieldLevel = self.getTextStatic('lfp', 'level') textOctaveLevel = self.getTextStatic('lop', 'level') textPitchSelectorControl = self.getTextStatic('psc', 'selectionString') #textNonRedundantSwitch = self.getTextStatic('nrs', 'onOff') # create a list of chords from the appropriate pitch mode for pathPos in self.getPathPos(): chordCurrent = self.getPitchGroup(pathPos) multisetCurrent = self.getMultiset(pathPos) tStartSet, tEndSet = self.clockPoints() # if textNonRedundantSwitch == 'on': selectorControl = 'randomPermutate' # else: selectorControl = 'randomChoice' selectorChordPos = basePmtr.Selector(range(len(chordCurrent)), textPitchSelectorControl) tStartSetReal = copy.deepcopy(tCurrent) self.stateUpdate(tCurrent, chordCurrent, None, multisetCurrent, None, None) if textFieldLevel == 'set': transCurrent = self.getField(tCurrent) # choose PITCHFIELD if textOctaveLevel == 'set': octCurrent = self.getOct(tCurrent) # choose OCTAVE while 1: # PITCH in CHORD if tCurrent >= tEndSet: break bpm, pulse, dur, sus, acc = self.getRhythm(tCurrent) # choose RHYTHM if acc == 0 and not self.silenceMode: # this is a rest tCurrent = tCurrent + dur continue # this ps should be used as ROOT; # this is _not_ implemented yet, however # choose PC from CHORD ps = chordCurrent[selectorChordPos()] self.stateUpdate(tCurrent, chordCurrent, ps, multisetCurrent, None, None) if textFieldLevel == 'event': transCurrent = self.getField(tCurrent) # choose PITCHFIELD if textOctaveLevel == 'event': octCurrent = self.getOct(tCurrent) # choose OCTAVE #subprocess psChord is a list of PCH's needed to make chord, psChord = [] for pitchSpace in chordCurrent: if textFieldLevel == 'voice': transCurrent = self.getField(tCurrent) # choose PITCHFIELD if textOctaveLevel == 'voice': octCurrent = self.getOct(tCurrent) # choose OCTAVE psReal = pitchTools.psToTempered(pitchSpace, octCurrent, self.temperamentObj, transCurrent) psChord.append(psReal) # amp and pan done for each chord, not voice amp = self.getAmp(tCurrent) * acc pan = self.getPan(tCurrent) #do this for each PCH in psChord, already transposed for psReal in psChord: self.stateUpdate(tCurrent, chordCurrent, pitchSpace, multisetCurrent, None, psReal) # choose aux for each voice auxiliary = self.getAux(tCurrent) eventDict = self.makeEvent(tCurrent, bpm, pulse, dur, sus, acc, amp, psReal, pan, auxiliary) self.storeEvent(eventDict) # parellel transposition offset = 0 for parallelVoice in textParallelVoiceList: #offset to avoid amp problems, correct error w/ offset tCurrent = tCurrent + textParallelDelayTime offset = offset + textParallelDelayTime psText = pitchTools.psTransposer(psReal, parallelVoice) eventDict = self.makeEvent(tCurrent, bpm, pulse, dur, sus, acc, amp, psText, pan, auxiliary) self.storeEvent(eventDict) #---------------------------- # move clocks forward by dur unit tCurrent = (tCurrent + dur) - offset self.clockForward() # advances path positon # return value to check for errors return 1
def _scoreMain(self): """creates score >>> from athenaCL.libATH.libTM import texture >>> ti = texture.factory('LineCluster') >>> ti.tmName == 'LineCluster' True >>> ti.loadDefault() >>> ti.score() == True True """ # texture-wide PATH/PITCH elements # texture-wide time elements inst = self.getInst() tStart, tEnd = self.getTimeRange() tCurrent = tStart # texture-wide TEXTURE (self.textQ amd)options #used for optional parallel voices textParallelVoiceList = self.getTextStatic('pml', 'transpositionList') textParallelDelayTime = self.getTextStatic('pml', 'timeDelay') # get field, octave selection method value textFieldLevel = self.getTextStatic('lfp', 'level') textOctaveLevel = self.getTextStatic('lop', 'level') textPitchSelectorControl = self.getTextStatic('psc', 'selectionString') #textNonRedundantSwitch = self.getTextStatic('nrs', 'onOff') # create a list of chords from the appropriate pitch mode for pathPos in self.getPathPos(): chordCurrent = self.getPitchGroup(pathPos) multisetCurrent = self.getMultiset(pathPos) tStartSet, tEndSet = self.clockPoints() # if textNonRedundantSwitch == 'on': selectorControl = 'randomPermutate' # else: selectorControl = 'randomChoice' selectorChordPos = basePmtr.Selector( list(range(len(chordCurrent))), textPitchSelectorControl) tStartSetReal = copy.deepcopy(tCurrent) self.stateUpdate(tCurrent, chordCurrent, None, multisetCurrent, None, None) if textFieldLevel == 'set': transCurrent = self.getField(tCurrent) # choose PITCHFIELD if textOctaveLevel == 'set': octCurrent = self.getOct(tCurrent) # choose OCTAVE while 1: # PITCH in CHORD if tCurrent >= tEndSet: break bpm, pulse, dur, sus, acc = self.getRhythm( tCurrent) # choose RHYTHM if acc == 0 and not self.silenceMode: # this is a rest tCurrent = tCurrent + dur continue # this ps should be used as ROOT; # this is _not_ implemented yet, however # choose PC from CHORD ps = chordCurrent[selectorChordPos()] self.stateUpdate(tCurrent, chordCurrent, ps, multisetCurrent, None, None) if textFieldLevel == 'event': transCurrent = self.getField(tCurrent) # choose PITCHFIELD if textOctaveLevel == 'event': octCurrent = self.getOct(tCurrent) # choose OCTAVE #subprocess psChord is a list of PCH's needed to make chord, psChord = [] for pitchSpace in chordCurrent: if textFieldLevel == 'voice': transCurrent = self.getField( tCurrent) # choose PITCHFIELD if textOctaveLevel == 'voice': octCurrent = self.getOct(tCurrent) # choose OCTAVE psReal = pitchTools.psToTempered(pitchSpace, octCurrent, self.temperamentObj, transCurrent) psChord.append(psReal) # amp and pan done for each chord, not voice amp = self.getAmp(tCurrent) * acc pan = self.getPan(tCurrent) #do this for each PCH in psChord, already transposed for psReal in psChord: self.stateUpdate(tCurrent, chordCurrent, pitchSpace, multisetCurrent, None, psReal) # choose aux for each voice auxiliary = self.getAux(tCurrent) eventDict = self.makeEvent(tCurrent, bpm, pulse, dur, sus, acc, amp, psReal, pan, auxiliary) self.storeEvent(eventDict) # parellel transposition offset = 0 for parallelVoice in textParallelVoiceList: #offset to avoid amp problems, correct error w/ offset tCurrent = tCurrent + textParallelDelayTime offset = offset + textParallelDelayTime psText = pitchTools.psTransposer(psReal, parallelVoice) eventDict = self.makeEvent(tCurrent, bpm, pulse, dur, sus, acc, amp, psText, pan, auxiliary) self.storeEvent(eventDict) #---------------------------- # move clocks forward by dur unit tCurrent = (tCurrent + dur) - offset self.clockForward() # advances path positon # return value to check for errors return 1
def psScale(self, pitchFormat, contourForm, psBase, microTone=.5): """translates a scale form in notation [0,-1,0,1] into various pitch representations. integers in the scale form are interpreted in three ways: as chromatic 1/2 steps, as diatonic scale pitches (either from the local set or the entire path), or as units of some microtonal size this returns a list representing the scale steps in relation to psBase psBase needs to be found in terms of the path, which may not consist only of ints returns a psCountourReference, which is alway tempered pitch values """ # not sure this needs to be an int #assert drawer.isInt(psBase) octCurrent = self.pmtrObjDict['octQ'].currentValue transCurrent = self.pmtrObjDict['fieldQ'].currentValue #currentChord = self.stateCurrentChord # not needed for all forms, but myst always get if pitchFormat == 'set': pitchGroup = self.refDict['stateCurrentChord'] elif pitchFormat == 'path': pitchGroup = self.refDict['statePathList'] else: # non given, but note used pitchGroup = self.refDict['stateCurrentChord'] refScales, lowerUpper = extractNeighbors(pitchGroup, psBase) pcContourDict = mapNeighbors(refScales, psBase, contourForm) #print pcContourDict # get pitch scale # this has the mapping with the appropriate pitches # N.B: danger here of getting mistransposed values # previously was an error and corrected in _splitPch psContourRef = [] if pitchFormat == 'chromatic': for entry in contourForm: # transpose before getting temperament pcSpace = pitchTools.psTransposer(psBase, entry) psReal = pitchTools.psToTempered(pcSpace, octCurrent, self.temperamentObj, transCurrent) psContourRef.append(psReal) # transpose by half steps # sets: derive scale from set elif pitchFormat == 'set' or pitchFormat == 'path': for entry in contourForm: pcSpace = pcContourDict[entry] # scale step is a key, gets pcSpace psReal = pitchTools.psToTempered(pcSpace, octCurrent, self.temperamentObj, transCurrent) psContourRef.append(psReal) # transpose by half steps elif pitchFormat == 'microtone': # microtonal for entry in contourForm: # treat scale step as microtone scaler # must do transposition after converting to PCH if entry * microTone > entry * 2: environment.printWarn([lang.WARN, 'microtone large (%s)' % (entry * microTone)]) trans = (transCurrent + (entry * microTone)) psReal = pitchTools.psToTempered(psBase, octCurrent, self.temperamentObj, trans) psContourRef.append(psReal) # transpose by half steps else: raise ValueError, 'no such pitchFormat' # this now returns psReals, not pch values return psContourRef, refScales
def extractNeighbors(pitchGroup, baseNote, scales=None): """takes a set, or a whole path, and derives a pc scale, a pitch space scale, and provides the upper and lower note to baseNote baseNote should be represented in the pitch group we need to know our current reference position in the pitchGroup pitchGroup has pitch pre-temperament; thus baseNote should be pre-temperament may be a psReal pitchGroup: from a refDict, containing stateCurrentChord, or statePathList will be a list of raw psReal values: could be floats, and could have micro specification """ # if scales given and no pitchGroup is given if scales != None and pitchGroup == None: colPitchSpace = scales[0] colPitchClass = scales[1] else: # given a set or path as pitchGroup if drawer.isNum(pitchGroup[0]): pitchGroup = [pitchGroup, ] # make all look like paths colPitchSpace = [] colPitchClass = [] for set in pitchGroup: for entry in set: if entry not in colPitchSpace: colPitchSpace.append(entry) # round; zero trans gets mod12 entryPC = pitchTools.pcTransposer(entry, 0) if entryPC not in colPitchClass: colPitchClass.append(entryPC) colPitchSpace.sort() colPitchClass.sort() scales = colPitchSpace, colPitchClass # use pitch class space to get neighbors # can use pitch space in the future? wrap around is strange octaveAdjust = 0 baseOctMult, basePC = pitchTools.splitOctPs(baseNote) # although baseNote may be a float (already tempered) # basePC seems to need to be an int, as it is used to find # a position in the scale; for this reason it seems like # the path positions value, and not the tempered pitch # should be coming in as the baseNote: tmerperament could cause # a rounding error and pass a pitch that is not in the scale at all #print _MOD, basePC, colPitchClass idx = None try: idx = colPitchClass.index(basePC) except ValueError: # not in the collected pitches; try rounding for i in range(len(colPitchClass)): # compare rounded versions, as floats may not match if round(colPitchClass[i], 2) == round(basePC, 2): idx = i # print _MOD, 'found rounded match' if idx == None: idx = 0 environment.printDebug('no match between base pitch and collected pitches') idxL = idx - 1 # lower neighbor if idxL == -1: # wrap index around idxL = len(colPitchClass) - 1 octaveAdjust = -1 idxU = idx + 1 # upper neighbor if idxU == len(colPitchClass): idxU = 0 octaveAdjust = 1 neighborL = colPitchClass[idxL] if octaveAdjust == -1: neighborL = pitchTools.psTransposer(neighborL, -12) neighborU = colPitchClass[idxU] if octaveAdjust == 1: neighborU = pitchTools.psTransposer(neighborU, 12) # do octave adjust ment relative to baseNote in pitch space neighborL = pitchTools.psTransposer(neighborL, (12 * baseOctMult)) neighborU = pitchTools.psTransposer(neighborU, (12 * baseOctMult)) lowerUpper = neighborL, neighborU return scales, lowerUpper