SC.py :  » Media-Sound-Audio » athenaCL » athenaCL » libATH » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » Media Sound Audio » athenaCL 
athenaCL » athenaCL » libATH » SC.py
#-----------------------------------------------------------------||||||||||||--
# Name:         SC.py
# Purpose:      utility functions and SC object for all set class operations.
#
# Authors:      Christopher Ariza
#
# Copyright:    (c) 2001-2007 Christopher Ariza
# License:      GPL
#-----------------------------------------------------------------||||||||||||--

# master data tables used for all set data access
from athenaCL.libATH import SCdata
from athenaCL.libATH import dialog
from athenaCL.libATH import drawer
from athenaCL.libATH import pitchTools
from athenaCL.libATH import language
from athenaCL.libATH import spectral
from athenaCL.libATH import sieve
from athenaCL.libATH import error
lang = language.LangObj()

SCDICT  = SCdata.SCDICT  # data for all sets, vectors and docs
TNMAX   = SCdata.TNMAX   # dictionary
TNIMAX  = SCdata.TNIMAX  # ref dictionary
TNREF   = SCdata.TNREF   # ref dcitionary
SCREF   = SCdata.SCREF   # ref dcitionary
forte   = SCdata.forte   # classic forte table

#-----------------------------------------------------------------||||||||||||--

import copy

_MOD = 'SC.py'
#-----------------------------------------------------------------||||||||||||--

# these function are depreciated here
# moved to pitchTools.py
# thes are provided for bkward compat
transposer = pitchTools.pcTransposer
pitchSpaceTransposer = pitchTools.psTransposer

#-----------------------------------------------------------------||||||||||||--
# tools for processing sets
# these methods are out of date (post 1.2) and should be phased out of usage

def pcSetTransposer(chord, trans):
   """transposes an entire set by trans. w/ mod12
   will strip oct info, retain micro info
   """
   newSet = []
   for pc in chord:
      newSet.append(pitchTools.pcTransposer(pc, trans))
   return tuple(newSet)
   
def psSetTransposer(chord, trans):
   """transposes an entire set by trans, no mod12
   retains oct info, micro info 
   """
   newSet = []
   for pc in chord:  ## works for negative or positive numbers
      newSet.append(pitchTools.psTransposer(pc, trans))
   return tuple(newSet)
   
def pcInverter(nrmlSet):
   """returns the inversion of a chord (list of pitches in normal form)"""
   tempSet = []
   invertSet = []
   for pitch in nrmlSet:
      tempSet.append((12 - pitch) % 12)
   tempSet.reverse()
   for pitch in tempSet:
      invertSet.append(transposer(pitch, (12 - tempSet[0])))
   return tuple(invertSet)

def psInverter(normalChord): 
   """returns the inversion of a chord (list of pitches in normal form)
   returns inversion with same starting value in pitch space
   must be entered as normal form
   """  
   modInversiontSet = pcInverter(normalChord)
   
   sourceSetAsOctMultipliers = []
   for entry in normalChord:
      octMultiplier, modPC = pitchTools.splitOctPs(entry)
      # gives original order of oct multipliers fo each member of set
      sourceSetAsOctMultipliers.append(octMultiplier)
   
   modInversiontSet = list(modInversiontSet)
   # do mod 12 transposition
   invertedChord = pcSetTransposer(modInversiontSet, normalChord[0])
  
   invertedChord = list(invertedChord)
   #check octaves
   for i in range(0,len(normalChord)) :
      sourceOct = sourceSetAsOctMultipliers[i]
      currentOct, modPC = pitchTools.splitOctPs(invertedChord[i])    
      if sourceOct == currentOct:
         pass
      else: # find difference and make up
        if sourceOct > currentOct:
           direction = 'up'
        else:
           direction = 'down'
        distance = abs(currentOct - sourceOct)
        if direction == 'up':
           invertedChord[i] = invertedChord[i] + (12 * distance)
        else:
           invertedChord[i] = invertedChord[i] - (12 * distance)        
   return tuple(invertedChord)

   
#-----------------------------------------------------------------||||||||||||--
def psSetToMason(chord):
   """named after a music educator named Mason by Michael Gogins
   convert any pitch space / class set to a 'mason' value
   OR-ing, to be precise, mod 4095 (the total number of unordered
   pitch-class sets in 12TET), not adding. In other words, a bit-field of 12
   bits, one bit per pitch-class.
   """
   newSet = []
   for pc in chord:
      pc = pitchTools.pcTransposer(pc, 0) # make mod 12
      if pc not in newSet: # remove redundancies
         newSet.append(pc)
   mason = 0
   for i in newSet:
      mason = mason + pow(2, i)
   return mason % 4095

#-----------------------------------------------------------------||||||||||||--
# utility to calculate normal form
def findNormalT(pcSet, setMatrix=None):
   """finds normal form of any pc set and returns forte number
   as a scTriple data structure, and transposition from normal form
   pcSet may contain psReals, and as such, need to be converted to ints
   """
   if setMatrix == None: # use forte as default
      setMatrix = forte
   MONADscTuple = (1,1,0)

   # check for bad data
   if drawer.isStr(pcSet):
      return None # error, no strings supported here
   if drawer.isList(pcSet):
      for psReal in pcSet:# make sure all values are numbers; no strings allowed
         if drawer.isStr(psReal):
            return None # break, return None as error
   # check for unusual data
   if drawer.isNum(pcSet): # its a single number
      pcVal = pitchTools.roundMicro(pcSet)
      return MONADscTuple, (pcVal % 12) # second number is transposition from 0
   if len(pcSet) == 1:   #filter out monad!
      pcVal = pitchTools.roundMicro(pcSet[0])
      return MONADscTuple, (pcVal % 12)

   # scrub and go
   pcSetClone = []
   for psReal in pcSet: # pcSet may contian psReal, w/ floating values
      pcSetClone.append(pitchTools.roundMicro(psReal))
   #check fr non base 12 numbers, negative numbers, redundancies
   pcSetClone = list(pcSetTransposer(pcSetClone, 0))
   pcSetClone.sort()

   i = 0
   chord = []
   for i in range(0,12):  # remove redundancies
      if i in pcSetClone:
         chord.append(i)
   card = len(chord)  
   if card < 1: # monad has already been filtered out
      return None # 2nd no is transposition from 0
   if card == 1: # this is a set like (3,3,3,3)
      return MONADscTuple, (pcSet[0] % 12) 
   elif card > 12:
      return None # 'irrational cardinality error'

   rotIndices = range(0, card)
   foundIndex = None           #control variable
   for rot in rotIndices:
      r      = rot # dont need to add 1? + 1
      rotSet = chord[r:card] + chord[0:r]
      dif    = rotSet[0]
      pSet   = pcSetTransposer(rotSet, -dif)
      iSet   = tuple(pcInverter(pSet))
      maxRange = len(setMatrix[card])
      # check all sets of given card for match
      for index in range(1, maxRange):  # start with 1, not zero
         # this is a default; may be a symmetrical set and have no inversion
         foundInv = 'A' 
         # test each set in this cardinality; "0" gets pitches
         testSet = tuple(setMatrix[card][index][0])  
         if iSet == testSet:
            foundIndex = index
            foundInv   = 'B'      #nt sure yet if 1 or 0
            break 
         elif pSet == testSet:
            foundIndex = index
            foundInv   = 'A'      #nt sure yet if 1 or 0 
            break
      if foundIndex != None:
         break
   if foundIndex == None:  ## no set found
      return None #'failed!!!'
   
   if foundInv == 'B':
      # has inversion that is non-redundant (variant)
      if setMatrix[card][foundIndex][2][1] == 0 :    
         scInv = -1
      else: scInv = 0
   elif foundInv == 'A':
       # has inversion that is non-redundant (variant)
      if setMatrix[card][foundIndex][2][1] == 0 :
         scInv = 1
      else: scInv = 0
   return (card, foundIndex, scInv), dif


def findNormal(pcSet, setMatrix=None):
   """same as above but w/o returning transposition"""
   scTuple, dif = findNormalT(pcSet, setMatrix)
   return scTuple


#-----------------------------------------------------------------||||||||||||--
# conversion utilities

def forteToSc(card, index, inversion=-2):
   """checks for proper inversion and supplies one (A) if not given
   acts as a general filter for all functions calling old forte numbers or
   possible errors: this function will check and suply an alternitive if 
   there is an error rather than raising an exception. its used heavily 
   and is a source of possible errors
   """
   boundError = 0
   if card > 12 or card < 1:
      boundError = 1
   else: scCard = card         #all other cards are good + used

   # checks cardinality fr a valid index number
   # supplies index 1 fr a valid card that does nt have
   # a valid index. !!!!!!!!!! should return an error!
   
   if card == 1 or card == 11:
      if index <= 0 or index >2:
         boundError = 1
      else: scIndex = index
   if card == 2 or card == 10:
      if index <= 0 or index >6:
         boundError = 1
      else: scIndex = index
   elif card == 3 or card == 9:
      if index <= 0 or index > 12:
         boundError = 1
      else: scIndex = index
   elif card == 4 or card == 8:
      if index <= 0 or index > 29:
         boundError = 1
      else: scIndex = index
   elif card == 5 or card == 7:
      if index <= 0 or index > 38:
         boundError = 1
      else: scIndex = index
   elif card == 6:
      if index <= 0 or index > 50:
         boundError = 1
      else: scIndex = index
   elif card == 12:  ## aggregate!
      if index <= 0 or index > 2:
         boundError = 1
      else: scIndex = index

   if boundError:
      print _MOD, 'SC boundary error', card, index, inversion
      return None
   # check fr proper inversion status useing variance vector
   # if_no inv give fr a set, 0 or 1 is suplied, never -1
   # if_no inversion is supplied, acts as if_in Tn/i classification
          
   if inversion <= -2 or inversion >= 2 or inversion == 0:
      if forte[scCard][scIndex][2][1] == 0 :    
      # has inversion that is non-redundant (variant)
         scInv = 1
      else: scInv = 0
   if inversion == -1:
      if forte[scCard][scIndex][2][1] == 0 :
         scInv = -1
      else: scInv = 0
   if inversion == 1:
      if forte[scCard][scIndex][2][1] == 0 :
         scInv = 1   
      else: scInv = 0

   return scCard, scIndex, scInv

def tupleToSc(rawForte):
   """accepts single tuple as input, then uses forte to sc to 
      to supply necessary inversion, if needed.
   """
   if rawForte[0] == 1:
      return (1,1,0) 
   try:
      inv = rawForte[2]
   except:
      inv = -2  #unknown inversion value: forte to sc will suply
   scTuple = forteToSc(rawForte[0], rawForte[1], inv)
   if scTuple == None:
      raise ValueError
   return scTuple

# scToStr method should be moved inside Multiset
def scToStr(rawForte):
   """raw fortte is a tuple with either 2 or 3 elements, needing to be 
      checked
   """
   if drawer.isInt(rawForte):
      return '1-1' 
   elif rawForte[0] == 1:
      return '1-1'     
   
   scTuple = tupleToSc(rawForte)   
   if len(scTuple) != 3: raise ValueError # should never happen
      
   card = str(scTuple[0])
   indx = str(scTuple[1])
   if scTuple[2] == 0:
      inv = ''
   elif scTuple[2] == 1:
      inv = 'A'
   elif scTuple[2] == -1:
      inv = 'B'
   else: inv = 'error'
   return card + '-' + indx + inv
      







#-----------------------------------------------------------------||||||||||||--

class SetClass:
   """object to provide a unifrom interface to SC data
      this is a library object; it store utility functions and reference data
      object, in general, should be passed; unique objects are not needed
      no permanent data stored in this object
      only use to acces data, not to process or display
   """
        
   def __init__(self):
      # backward compat tables
      self.REFdiaNameToPc = pitchTools.REFdiaNameToPc

      self.analysisDict = {
      'K'     : self.morris_K,           #  (self, forteX, forteY)   
      'pRel'  : self.castren_pRel,       #  (self, forteX, forteY, tni=1, n=2) 
      'R1'    : self.forte_R1,           #  (self, forteX, forteY)
      'R2'    : self.forte_R2,           #(self, forteX, forteY, strict = 1, 
                                         # min_matches = 4)
      'R0'    : self.forte_R0,           #  (self, forteX, forteY)
      'SI'    : self.teitelbaum_SI,      #  (self, forteX, forteY)
      'SIM'   : self.morris_SIM,         #  (self, forteX, forteY)
      'ASIM'  : self.morris_ASIM,        #(self, forteX, forteY)                
                                         #{1  -0(h.sim), avg=.42, d.v.=79}
      'sf'    : self.lord_sf,            #  (self, forteX, forteY)
      'IcVSIM': self.isaacson_IcVSIM,    #  (self, forteX, forteY)
      'IcVD1' : self.rogers_IcVD1,       #  (self, forteX, forteY)
      'IcVD2' : self.rogers_IcVD2,       #  (self, forteX, forteY)
      'COST'  : self.rogers_COST,        #(self, forteX, forteY)                
                                         #{0  -1(h.sim), avg=.81, d.v.=92}
      'Ak'    : self.rahn_Ak,            #(self, forteX, forteY)                
                                         #{0  -1(h.sim), avg=.58, d.v.=78}
      'MEMBn' : self.rahn_MEMBn,         #  (self, forteX, forteY,tni=1,n=2,)
      'TMEMB' : self.rahn_TMEMB,         #  (self, forteX, forteY, tni=1)
      'ATMEMB': self.rahn_ATMEMB,        #(self, forteX, forteY, tni=1)         
                                         #{0  -1(h.sim), avg=.45, d.v.=101}
      'REL'   : self.lewin_REL,          #(self, forteX, forteY, tni=1)         
                                         #{0  -1(h.sim), avg=.57, d.v.=91}
      'TpRel' : self.castren_TpREL,      #(self, forteX, forteY, tni=1)         
                                         #{100-0(h.sim), avg= 63, d.v.=79}
      }

   #-----------------------------------------
   # bacward compat functions
   # will be removed with multiset
   def forteToSc(self, card, index, inversion=-2):
      return forteToSc(card, index, inversion)

   def tupleToSc(self, rawForte):
      return tupleToSc(rawForte)

   def scToStr(self, rawForte):
      return scToStr(rawForte)

   # will be depreciated, as now in pitch tools
   def anySetToPcs(self, set):
      # set can contain any int, positive or neg
      # input must be a list
      pcsSet = []
      for entry in set:
         modulatedEntry = transposer(entry, 0)
         pcsSet.append(modulatedEntry)
      pcsSet = tuple(pcsSet)
      return pcsSet

   #-----------------------------------------
   # data access
   def rawSetData(self, card, index, inv, key):
      'access data as from list coordinants'
      return SCDICT[card][index,inv][key]
      
   def getAllScTriples(self, cardRange='all', tniTog=0):
      """gets all scTriples within a variety of ranges
      card range can be specified as string 'all', 
      as an int (getting just the values of that int
      or as a range from 1 to 12; if range, last values is inclusif"""
      if cardRange == 'all':
         gatherCards = range(1,13)
      elif drawer.isInt(cardRange):
         gatherCards = [cardRange,] # only get one card
      elif drawer.isList(cardRange):
         if cardRange[1] < cardRange[0]:
            raise ValueError, 'bad cardinality range given'
         elif cardRange[0] == cardRange[1]:
            gatherCards = [cardRange[0],] # only get one card
         else:
            gatherCards = range(cardRange[0], cardRange[1]+1)
      found = []
      for scTriple in TNREF.keys():
         card = scTriple[0]
         inv = scTriple[2]
         if card in gatherCards:
            if tniTog and inv == -1: pass # leave out inversions
            else:
           found.append(scTriple)
      found.sort()
      return found

               
    #---------------------||||||||||||--               
   # these functions are used fr calling any data entry frm
   # the SCDICT via a SC class instance (as Sc n Athena)
   # AUTOVALIDATED with sc_to_forte.
   # if_no inversion is supplied, acts as if_in Tn/i classification
    #  -2 is the unknown (test and suply) integer, if nt suplied when called
   
   def pcs(self, rawForte):     
      scTuple = self.tupleToSc(rawForte)
      return SCDICT[scTuple[0]][scTuple[1], scTuple[2]][0]
   def var(self, rawForte):
      scTuple = self.tupleToSc(rawForte)
      return SCDICT[scTuple[0]][scTuple[1], scTuple[2]][1]
   def icv(self, rawForte):
      scTuple = self.tupleToSc(rawForte)
      return SCDICT[scTuple[0]][scTuple[1], scTuple[2]][2]


   def cv(self, rawForte, n):
      """get tni vector for a forte and an n size"""
      if n > 12 or n < 2:
         return 'error in cv n value'
      scTuple = self.tupleToSc(rawForte)
      # translate a 2cv to an icv vector
      if n == 2:
         key = 2
      # find position in SC data tuple
      else:
         card  = scTuple[0]
         if n > card:
            return 'n/a'
         shift = 2 # positional shift nec. to get the right data string
         key   = shift + (n-2)
      try:
         value = SCDICT[scTuple[0]][scTuple[1], scTuple[2]][key]
      except:
         value = 'n/a'
      return value


   def xv(self, rawForte, n):
      """get tn vector for a forte and an n size"""
      if n > 12 or n < 2:
         return 'error in xv n value'
      scTuple = self.tupleToSc(rawForte)
      # translate a 2cv to an icv vector
      if n == 2:
         key = 2
      # find position in SC data tuple
      else:
         card  = scTuple[0]
         if n > card:
            return 'n/a'    
         # card-2 is the number of positions to move over ot get xvectors     
         shift = 2 + (card-2) 
         key   = shift + (n-2)
      try:
         value = SCDICT[scTuple[0]][scTuple[1], scTuple[2]][key]
      except:
         value = 'n/a'
      return value


   def _findVectorPos(self, rawForte, tniMode=0):
      """for a given set, finds the appropriate vector position
         given the appropriate tni mode """
      scTuple = self.tupleToSc(rawForte)
      card, index, inv = scTuple
      if tniMode == 1: # tni mode
         vectorPos = index - 1 # subtract one to get list position
      else: # tn mode
         vectorPos = TNREF[scTuple] - 1
      return vectorPos

   def _findVectorVal(self, rawForteBase, rawForteSeek, tniMode=0):
      """gets the value from a vector belong to forteBase; value is number
         of subsets like forteSeek
      """
      scTupleBase = self.tupleToSc(rawForteBase)
      scTupleSeek = self.tupleToSc(rawForteSeek)
      cardBase, indexBase, invBase = scTupleBase
      cardSeek, indexSeek, invSeek = scTupleSeek

      if cardSeek > cardBase: # no vectors of greater card than source
         return None

      vPos = self._findVectorPos(rawForteSeek, tniMode)
      if tniMode == 1: #tni mode
         vector = self.cv(scTupleBase, cardSeek)
      else: #tn mode
         vector = self.xv(scTupleBase, cardSeek)
      return vector[vPos]
       

   def zData(self, rawForte):         
      """returns sc tuple of z relation, if it exists
         otherwise, returns none
      """        
      scTuple = self.tupleToSc(rawForte)
      zVal = forte[scTuple[0]][scTuple[1]][3] # gets z relation val
      # z val is index of relative z relation
      if zVal == 0:
         return None
      else:
         # find card complement
         card = scTuple[0]
         return self.tupleToSc((card, zVal))

   def refData(self, rawForte):
      """returns dictionary of references from SCdata"""
      scTuple = self.tupleToSc(rawForte)
      setRef = SCREF[scTuple]
      if setRef == {} or setRef == None:
         return None
      else:
         return setRef


   def findSuperSets(self, rawForte, cardRange='all', tniMode=0):
      """find all sets with equal or greater card that have this
         set as a subset
      """
      scTupleSeek = self.tupleToSc(rawForte)
      card, index, inv = scTupleSeek
      if card >= 1 and card <= 12:
         if cardRange == 'all':
            cardsToSearch = range(card, 13)
         setList = self.getAllScTriples(cardsToSearch, tniMode)

      valueDict = {}
      for set in setList:
         vectorVal = self._findVectorVal(set, scTupleSeek, tniMode)
         if vectorVal != 0:     
            valueDict[set] = vectorVal

      searchResults = []
      rankList = []
      for setTuple in valueDict.keys():
         rankList.append((valueDict[setTuple], setTuple))
      rankList.sort()
      rankList.reverse()
      for rank, setTuple in rankList:
         searchResults.append(setTuple) # ordered by rank
      if searchResults == []:
         return None
      return searchResults, valueDict

   def findAllZ(self, cardRange='all', tniMode=0):
      """return a list of triples for a all sets that have a z relations"""
      searchResults = []
      for setTriple in self.getAllScTriples('all', tniMode):
         if self.zData(setTriple) != None:
            searchResults.append(setTriple)
      return searchResults

   def _strToSearchList(self, str):
      """removes bad characters, returns a list of words"""
      str = str.replace('-',' ')
      str = str.replace(',',' ')
      str = str.replace('/',' ')
      str = str.replace('\ ', ' ')
      strList = str.split() # returns a list
      return strList        

   def findRef(self, searchStr, refType='name', setRange='all', tniMode=0):
      searchWords = self._strToSearchList(searchStr) # returns a list
      scoreDict = {}
      for setTuple in self.getAllScTriples('all', tniMode):
         refDict = self.refData(setTuple)
         scoreDict[setTuple] = 0
         if refDict == None:
            continue
         if refDict.has_key(refType): # name groups
            nameList = refDict[refType]
            nameWords = []
            for nameStrings in nameList: # list of strings
               nameWords = nameWords + self._strToSearchList(nameStrings)         
            for sw in searchWords:
               swTemp = sw.lower() # keep case
               for nw in nameWords:
                  nwTemp = nw.lower() # keep case
                  if nwTemp.find(swTemp) >= 0:
                     scoreDict[setTuple] = scoreDict[setTuple] + 1 # add point
      rankList = []
      for setTuple in scoreDict.keys():
         if scoreDict[setTuple] == 0:
            del scoreDict[setTuple] # remove if 0 score
         else: # add ranks to a list
            rankList.append((scoreDict[setTuple], setTuple))
      rankList.sort()
      rankList.reverse()      
      searchResults = []
      for rank, setTuple in rankList: # ordered
         searchResults.append(setTuple)
      if searchResults == []:
         return None
      else:
         return searchResults # list of triples


   #---------------------||||||||||||-- 
   # sim utilities   

   def _sumVector(self, vector):
      sum = 0
      for reg in vector:
         sum = sum + reg
      return sum

   def _pcentVector(self, vector):
      '%v'
      total = self._sumVector(vector)
      p_v = []
      for reg in vector:
         percent = (reg / float(total)) * 100
         # if rounded here, error emerge in_castren_pRel
         # percent = round(percent)
         # percent = int(percent)
         p_v.append(percent)   
      return p_v
      
   def _difVector(self, vectorX, vectorY):
      """difference vector"""
      # this_vector should be a % vector before processing
      max = len(vectorX)
      difVectorX = []
      difVectorY = []
      for i in range(0, max):
         if vectorX[i] == vectorY[i]:
            difVectorX.append(0)
            difVectorY.append(0)
         elif vectorX[i] > vectorY[i]:
            difVectorX.append(vectorX[i] - vectorY[i])
            difVectorY.append(0)
         elif vectorY[i] > vectorX[i]:
            difVectorX.append(0)
            difVectorY.append(vectorY[i] - vectorX[i])
      difVectorX = tuple(difVectorX)
      difVectorY = tuple(difVectorY)
      return(difVectorX, difVectorY)

   def _scaledDifVector(self, vectorX, vectorY):
      """scaled difference vector"""
      # this_vector should be a % vector before processing
      difVectorX, difVectorY = self._difVector(vectorX, vectorY)
      sd_vector_x = self._pcentVector(difVectorX)
      sd_vector_y = self._pcentVector(difVectorY)
      return (sd_vector_x, sd_vector_y)

   def _scToSdv(self, forteX, forteY, tni=1, n=2):
      """converts 2 set classes to scaled difference vectors"""
      #_if tni == 1,(true_, TnI) uses cvN vectors (smaller)
      #_if tni == 0 (false_,Tn), uses cxN vectors (larger)
      if n > forteX[0] or n > forteY[0]:
         return 'no subset under this cardinality'
      if tni == 1:  #TnI#
         vectorX = self.cv(forteX, n)
         vectorY = self.cv(forteY, n)
      else:         #Tn #
         vectorX = self.xv(forteX, n)
         vectorY = self.xv(forteY, n)          
      p_vector_x                 = self._pcentVector(vectorX)
      p_vector_y                 = self._pcentVector(vectorY)
      sdv_vector_x, sdv_vector_y = self._scaledDifVector(p_vector_x, p_vector_y)
      return tuple(sdv_vector_x), tuple(sdv_vector_y)

   def _vPosToSc(self, vector_position, tni=1, n=2):
      if tni == 1:  #TnI#
         return (n, vector_position + 1)
      else:         #Tn #
         for key, value in TNREF.items():
            if key[0] == n and value == vector_position + 1:
               return key

   def _vToValueSc(self, sd_vector, tni=1, n=2):
      """process scalled difference vector only"""
      pairs = []
      for i in range(0, len(sd_vector)):
         if sd_vector[i] == 0:
            continue
         set = self._vPosToSc(i, tni, n)
         pairs.append((sd_vector[i], set))
      return pairs

   def _difGroup(self, forteX, forteY, tni=1, n=2):
      """creates weight, sc pair lists"""
      sdv_vector_x, sdv_vector_y = self._scToSdv(forteX, forteY, tni, n)
      pair_x = self._vToValueSc(sdv_vector_x, tni, n)
      pair_y = self._vToValueSc(sdv_vector_y, tni, n)
      return tuple(pair_x), tuple(pair_y)

   def _crossGroup(self, pair_x, pair_y, tni=1, pRel_n=2):
      """process only weight/sc pair lists"""
      group = []
      for weight_x, sc_x in pair_x:
         for weight_y, sc_y in pair_y:
            Wp   = (weight_x * weight_y) / 100.0
            pRel = self.castren_pRel(sc_x, sc_y, tni, pRel_n)
            vW   = (pRel * Wp) / 100.0
            group.append((sc_x, sc_y, vW))
      return group


   #---------------------||||||||||||-- 
   # NOTE these methods use the doc string to store the min
   # max and average value of each of these functions

   def morris_K(self, forteX, forteY, tni=1):  #castren, p37
      '(0, 55, 10, 35)'  #min sim, max sim, avg, values
      icv_x = self.icv(forteX)
      icv_y = self.icv(forteY)
      minimum = []
      for i in range(0,6):   #icv vector indices 0-5     
         if icv_x[i] < icv_y[i]:
            minimum.append(icv_x[i])
         else: minimum.append(icv_y[i])
      sum = self._sumVector(minimum)      
      return sum
 
 
   def castren_pRel(self, forteX, forteY, tni=1, n=2):  #castren, p39
      '(100, 0, 30, 85)'  #min sim, max sim, avg, values
      #_if tni == 1,(true_, TnI) uses cvN vectors (smaller)
      #_if tni == 0 (false_,Tn), uses cxN vectors (larger)
      if n > forteX[0] or n > forteY[0]:
         return 'no subset under this cardinality'
      if tni == 1:  #TnI#
         vectorX = self.cv(forteX, n)
         vectorY = self.cv(forteY, n)
         max = TNIMAX[n]
      else:         #Tn #
         vectorX = self.xv(forteX, n)
         vectorY = self.xv(forteY, n)          
         max = TNMAX[n]
      p_v_x = self._pcentVector(vectorX)
      p_v_y = self._pcentVector(vectorY)
      temp = []
      for i in range(0,max):
         dif = abs(p_v_x[i] - p_v_y[i])
         temp.append(dif)
      sum = self._sumVector(temp)
      return round((sum / 2.0), 1)


      


   #---------------------||||||||||||--  
   # sim relations not_producing values
 
   def forte_R1(self, forteX, forteY, tni=1):  #castren, p43
      '0'
      icv_x = self.icv(forteX)
      icv_y = self.icv(forteY)
      match_indices = []
      nomat_indices = []
      for i in range(0,6):   #icv vector indices 0-5     
         if icv_x[i] == icv_y[i]:
            match_indices.append(i)
         else:
            nomat_indices.append(i)
      if len(match_indices) != 4:
         return 0
      if (icv_x[nomat_indices[0]] == icv_y[nomat_indices[1]] and 
          icv_x[nomat_indices[1]] == icv_y[nomat_indices[0]]):
         return 1
      else: return 0  
    
   def forte_R2(self, forteX, forteY, tni=1, strict=1, min_matches=4):  
      #castren, p44
      '0'
      # strict is used to follow strict forte rules, that is, the 2 
      # left-over vector registers
      # can not_hold the r1-like cross relation. if_strict == 0 (false_),
      # this_test 
      # only finds the minimum number of matches needed (4 the default_)
      icv_x = self.icv(forteX)
      icv_y = self.icv(forteY)     
      match_indices = []
      nomat_indices = []
      for i in range(0,6):   #icv vector indices 0-5     
         if icv_x[i] == icv_y[i]:
            match_indices.append(i)
         else:
            nomat_indices.append(i)
      # THIS IS FORTES EXACT DEFINITION
      if strict == 1:  
         if len(match_indices) != 4:
            return 0
         # this_is R1
         if (icv_x[nomat_indices[0]] == icv_y[nomat_indices[1]] and 
             icv_x[nomat_indices[1]] == icv_y[nomat_indices[0]]):
            return 0       
         else: return 1        
      # THIS IS A MORE FLEXIBLE TEST
      else:
         if len(match_indices) < min_matches:
            return 0
         else:
            return 1
         

   def forte_R0(self, forteX, forteY, tni=1):  #castren, p46
      '0'
      # find minimum similarity
      icv_x = self.icv(forteX)
      icv_y = self.icv(forteY)
      match_indices = []
      for i in range(0,6):   #icv vector indices 0-5     
         if icv_x[i] == icv_y[i]:
            match_indices.append(i)
      if len(match_indices) == 0:
         return 1
      else: return 0       
      
 
 # skipped b/c very very week
 # Alphonce: Difference Value Relation (castren p49)
 # Solomon: R relation (castren p50)
 
    
   #---------------------||||||||||||--  
   # sim relations comparing one subset-class_cardinality at a time
    
    
   def teitelbaum_SI(self, forteX, forteY, tni=1):  #castren, p51
      '(8.49, 1.41, 2.85, 31)'  #min sim, max sim, avg, values
      if forteX[0] != forteY[0]:
         return 'n/a'
      icv_x = self.icv(forteX)
      icv_y = self.icv(forteY)   
      temp = []
      for i in range(0,6):   #icv vector indices 0-5     
         value = pow((icv_x[i] - icv_y[i]), 2)
         temp.append(value)
      sum = self._sumVector(temp)
      return pow(sum, .5)  #round?
      
   def morris_SIM(self, forteX, forteY, tni=1):  #castren, p54
      '(65, 0, 13, 44)'  #min sim, max sim, avg, values
      icv_x = self.icv(forteX)
      icv_y = self.icv(forteY)   
      temp = []
      for i in range(0,6):                    #icv vector indices 0-5     
         value = abs(icv_x[i] - icv_y[i])
         temp.append(value)
      sum = self._sumVector(temp)
      return sum
    
   def morris_ASIM(self, forteX, forteY, tni=1):  #castren, p59
      '(1, 0, .42, 79)'  #min sim, max sim, avg, values
      sim = self.morris_SIM(forteX, forteY)
      icv_x = self.icv(forteX)
      icv_y = self.icv(forteY) 
      sum = self._sumVector(icv_x) + self._sumVector(icv_y)  # add '#ICV'
      return sim / float(sum) # may result in divid by zero error

   def lord_sf(self, forteX, forteY, tni=1):  #castren, p61
      '(9, 0, 3, 10)'  #min sim, max sim, avg, values
      if forteX[0] != forteY[0]:
         return 'n/a'   
      sim = self.morris_SIM(forteX, forteY)
      return sim / 2.0
    
   def isaacson_IcVSIM(self, forteX, forteY, tni=1):  #castren, p62
      '(3.64, 0, 1.2, 121)'  #min sim, max sim, avg, values
      # not_perfect correspondance with castren's results
      # see notes on page 65

      icv_x = self.icv(forteX)
      icv_y = self.icv(forteY) 
      IdV = []
      for i in range(0,6):                    #icv vector indices 0-5     
         value = abs(icv_x[i] - icv_y[i])
         IdV.append(value)     
      sum_IdV     = self._sumVector(IdV) 
      average_IdV = sum_IdV / 6.0             #always 6 for_icv's

      sum = 0
      for IdV_reg in IdV:
         sum = sum + pow((IdV_reg - average_IdV), 2)
      return pow((sum / 6.0), .5)  

 
   def rogers_IcVD1(self, forteX, forteY, tni=1):  #castren, p67
      '(2, 0, .59, 140)'  #min sim, max sim, avg, values
      icv_x = self.icv(forteX)
      icv_y = self.icv(forteY) 

      sum_x = self._sumVector(icv_x)
      sum_y = self._sumVector(icv_y)

      temp = []
      for i in range(0,6):                    #icv vector indices 0-5     
         value = abs((icv_x[i]/float(sum_x)) - (icv_y[i]/float(sum_y)))
         temp.append(value)     
      return self._sumVector(temp) 

   def rogers_IcVD2(self, forteX, forteY, tni=1):  #castren, p67
      '(1.41, 0, .54, 133)'  #min sim, max sim, avg, values
      icv_x = self.icv(forteX)
      icv_y = self.icv(forteY) 

      sum_x = 0
      for elem in icv_x:
         sum_x = sum_x + pow(elem, 2)
      rad_x = pow(sum_x, .5)

      sum_y = 0
      for elem in icv_y:
         sum_y = sum_y + pow(elem, 2)
      rad_y = pow(sum_y, .5)

      temp = []
      for i in range(0,6):        #icv vector indices 0-5     
         value = pow(((icv_x[i]/rad_x) - (icv_y[i]/rad_y)), 2)
         temp.append(value)   
      sum = self._sumVector(temp)

      return pow(sum, .5)



   def rogers_COST(self, forteX, forteY, tni=1):  #castren, p71
      '(0, 1, .81, 92)'  #min sim, max sim, avg, values
      icv_x = self.icv(forteX)
      icv_y = self.icv(forteY) 

      numerator = 0
      for i in range(0,6):
         numerator = numerator + (icv_x[i] * icv_y[i])

      sum_x = 0
      for elem in icv_x:
         sum_x = sum_x + pow(elem, 2)
      rad_x = pow(sum_x, .5)

      sum_y = 0
      for elem in icv_y:
         sum_y = sum_y + pow(elem, 2)
      rad_y = pow(sum_y, .5)

      denominator = rad_x * rad_y
      if denominator == 0:
         print 'divide by zero error'
         result = 0
      result = numerator / denominator
      return result



   def rahn_Ak(self, forteX, forteY, tni=1):  #castren, p73
      '(0, 1, .58, 78)'  #min sim, max sim, avg, values
      # ak(X,Y) = (1-ASIM(X,Y))

      numerator = 2 * self.morris_K(forteX, forteY)

      icv_x = self.icv(forteX)
      icv_y = self.icv(forteY) 
      sum_x = self._sumVector(icv_x)
      sum_y = self._sumVector(icv_y)
      denominator = sum_x + sum_y

      return numerator / float(denominator)


   def rahn_MEMBn(self, forteX, forteY, tni=1, n=2):  #castren, p75
      '(0, 121, 30, 79)'  #min sim, max sim, avg, values
      #_if tni == 1,(true_, TnI) uses cvN vectors (smaller)
      #_if tni == 0 (false_,Tn), uses cxN vectors (larger)
      if n > forteX[0] or n > forteY[0]:
         return 'no subset under this cardinality'
      if tni == 1:  #TnI#
         vectorX = self.cv(forteX, n)
         vectorY = self.cv(forteY, n)
         max = TNIMAX[n]
      else:         #Tn #
         vectorX = self.xv(forteX, n)
         vectorY = self.xv(forteY, n)          
         max = TNMAX[n]

      MEMBn = 0
      for i in range(0, max):
         if vectorX[i] == 0 or vectorY[i] == 0:
            continue
         else:
            MEMBn = MEMBn + (vectorX[i] + vectorY[i])
      return MEMBn

   #---------------------||||||||||||--  
   # total measures

   def rahn_TMEMB(self, forteX, forteY, tni=1):  #castren, p80
      '(0, 6118, 131, 877)'  #min sim, max sim, avg, values
      # not_perfect correspondance with castren's results
      # see notes on page 84 (error of a single digit: 440/441)

      if forteX[0] <= forteY[0]:
         n = forteX[0]
      else: 
         n = forteY[0]
      TMEMB = 0
      for n_subset in range(2, (n + 1)):
         value = self.rahn_MEMBn(forteX, forteY, tni, n_subset)
         TMEMB = TMEMB + value
      return TMEMB


   def rahn_ATMEMB(self, forteX, forteY, tni=1):  #castren, p84
      '(0, 1, .45, 101)'  #min sim, max sim, avg, values
      # all numbers match castren, impying possible errors_in
      # rahn_TMEMB unimportant

      if forteX[0] <= forteY[0]:
         n = forteX[0]
      else: 
         n = forteY[0]
      TMEMB = 0
      for n_subset in range(2, (n + 1)):
         value = self.rahn_MEMBn(forteX, forteY, tni, n_subset)
         TMEMB = TMEMB + value
      denominator = ((pow(2, forteX[0]) + pow(2, forteY[0])) - 
                    (forteX[0] + forteY[0] + 2))
      return TMEMB / float(denominator)


   def lewin_REL(self, forteX, forteY, tni=1):  #castren, p89
      '(0, 1, .57, 91)'  #min sim, max sim, avg, values

      # most numbers match castren, with occassional round errors

      #_if tni == 1,(true_, TnI) uses cvN vectors (smaller)
      #_if tni == 0 (false_,Tn), uses cxN vectors (larger)

      if forteX[0] <= forteY[0]:
         n = forteX[0]
      else: 
         n = forteY[0]

      V_tot   = 0
      W_tot_x = 0
      W_tot_y = 0
      for n_subset in range(2, (n + 1)): 
         if tni == 1:  #TnI#
            vectorX = self.cv(forteX, n_subset)
            vectorY = self.cv(forteY, n_subset)
            max = TNIMAX[n_subset]      #number of registers in_vector
         else:         #Tn #
            vectorX = self.xv(forteX, n_subset)
            vectorY = self.xv(forteY, n_subset)          
            max = TNMAX[n_subset] 

         for i in range(0, max):
            V_tot = V_tot + pow((vectorX[i] * vectorY[i]), .5)

         sum_x    = self._sumVector(vectorX)
         W_tot_x  = W_tot_x + sum_x
         sum_y    = self._sumVector(vectorY)
         W_tot_y  = W_tot_y + sum_y
      try:
         REL = V_tot / pow((W_tot_x * W_tot_y), .5)
      except ZeroDivisionError:
         print "divide by zero error"
         REL = 0
      return REL


   def castren_TpREL(self, forteX, forteY, tni=1):  #castren, p89
      '(100, 0, 63, 79)'  #min sim, max sim, avg, values
      #_if tni == 1,(true_, TnI) uses cvN vectors (smaller)
      #_if tni == 0 (false_,Tn), uses cxN vectors (larger)

      if forteX[0] == forteY[0]:
         n = forteX[0] - 1
      elif forteX[0] < forteY[0]: 
         n = forteX[0]
      else: 
         n = forteY[0]

      numerator = 0
      for n_subset in range(2, (n + 1)): 
         if tni == 1:  #TnI#
            vectorX = self.cv(forteX, n_subset)
            vectorY = self.cv(forteY, n_subset)
            max = TNIMAX[n_subset]      #number of registers in_vector
         else:         #Tn #
            vectorX = self.xv(forteX, n_subset)
            vectorY = self.xv(forteY, n_subset)          
            max = TNMAX[n_subset] 
         pv_x = self._pcentVector(vectorX)
         pv_y = self._pcentVector(vectorY)
         sum = 0
         for i in range(0, max):
            sum = sum + abs(pv_x[i] - pv_y[i])         
         numerator = numerator + (sum / 2.0)
      return numerator / (n - 1)

   #---------------------||||||||||||--  

   def _castrenLevel(self, forteX, forteY, level):
      # branch acts like n on the local level, not_to be confused with a level
      #
      tni = 0  # always use Tn classification for_castren

      # produces weighted difference vectors based on two sets
      # level == cardinality of found subsets
      # listed as: weight, SC

      x, y = self._difGroup(forteX, forteY, tni, level)

      # matches all dif vectors in_each group, calculates weight
      # n must be one less than current level
      # listed as: SCX, SCY, weight
      
      sub_group = self._crossGroup(x, y, 0, level-1)

      # each pair of sets in_new_group needs to calc dif group, and_then 
      # produce a new_group

      return sub_group

   def _castrenBranch(self, forteX, forteY, branch):

      if branch == 4:
         

         level = branch
         key = (level,)
         temp = self._castrenLevel(forteX, forteY, level)

         Dict = {}
         Dict[key] = temp

         level = level - 1
         counter = 0
         for sc_x, sc_y, weight in Dict[key]:
            temp = self._castrenLevel(sc_x, sc_y, level)         
            key = (level, counter)
            Dict[key] = temp
            counter = counter + 1

         # my levels are different than castren's

         lvl_3_keys = [] 
         lvl_4_keys = [] 
         h = Dict.keys()
         h.sort()
         for key in h:
            if key[0] == 3:
               lvl_3_keys.append(key) 
            elif key[0] == 4:
               lvl_4_keys.append(key) 
         

         lvl_3_sums = []
         for key in lvl_3_keys:
            temp = 0
            for sc_x, sc_y, vW in Dict[key]:
               temp = temp + vW
            lvl_3_sums.append(temp)

         lvl_4_sums = []
         for key in lvl_4_keys:
            temp = 0
            index = 0
            for sc_x, sc_y, vW in Dict[key]:
               temp = (vW * lvl_3_sums[index]) / 100.0
               lvl_4_sums.append(temp)

         lvl_top = self.castren_pRel((7,35), (4,22,1), 0, branch)
         lvl_4_tot = self._sumVector(lvl_4_sums)

         branch_value = (lvl_top * lvl_4_tot) / 100.0

      return branch_value

   #---------------------||||||||||||--  
   # general interface for analysis measures
   def analysis(self, method, scX, scY, tni):
      """ check if its a scTriple or a set object
      may raise a divide by zero error
      """
      assert method in self.analysisDict.keys()

      if hasattr(scX, 'get') and hasattr(scY, 'get'):
         return self.analysisDict[method](scX.get('sc'), scY.get('sc'), tni)
      elif drawer.isList(scX) and drawer.isList(scY):
         return self.analysisDict[method](scX, scY, tni)
      else:
         raise ValueError, 'incorrect data format for analysis'

   def analysisRange(self, method, data=None):
      """get info on analyis ranges striped from the doc attribute
      if no data requested, info returned as a list
      """
      rl = eval(self.analysisDict[method].__doc__)
      rDict = {}
      if rl == 0 or rl == None:
         rDict['min'] = 0.0 # defaults
         rDict['max'] = 1.0
         rDict['avg'] = None
         rDict['val'] = None # values
      else:
         rDict['min'] = rl[0]
         rDict['max'] = rl[1]
         rDict['avg'] = rl[2]
         rDict['val'] = rl[3]
      return rDict

   def analysisRangeStr(self, method, w=12):
      rDict = self.analysisRange(method)
      sideRange = (w - 2) / 2 
      rString = (str(rDict['min']).rjust(sideRange) + '--' + 
                 str(rDict['max']).ljust(sideRange))
      return rString

   def analysisNames(self):
      return self.analysisDict.keys()

#-----------------------------------------------------------------||||||||||||--

class Multiset:
   """object of a set which may be interpreted as a set
   pcs, ps, or setclass. order and multiplcity may or may not matter
   thus it is called a multiset
   object orientated structure
   """
   
   def __init__(self, psRealSrc=None, scTriple=None, scObj=None):
      """ store scObj for reference
      _psRealSrc is stored as original data entered; not transposed or changed
      and should not be read as data
      scTriple stores forte name as data strcuture
      must be update for all changes
      psList is the internal data representation
      """
      if scObj == None:
         scObj = SetClass()
      self.scObj = scObj
      self.forms = ('midi', 'psReal', 'psName', 'pch', 'fq', 'pc', # pitch obj
                    'sc', 'dur', 'normal', 'mason', 'card') 
                    # sone only found in Multiset
      # it is questionable if the t is still required
      # stores transposition away from normal form, and accumulates other
      # transpositions
      self.tRef = 0
      self.dur  = 1 # a value for durational weighting, default

      if psRealSrc != None and scTriple != None: # both given
         self._psRealSrc = psRealSrc
         self._scTriple = scTriple

      elif psRealSrc != None and scTriple == None: # only ps given
         self._psRealSrc = psRealSrc
         normData = findNormalT(self._psRealSrc)
         if normData == None: # an erro has happend
            #print 'problem w/', _psRealSrc
            raise error.MultisetError # cancel set
         self._scTriple, self.tRef = normData

      elif psRealSrc == None and scTriple != None: # only sc given
         self._scTriple = scTriple
         self._psRealSrc = self.scObj.pcs(self._scTriple)
      else: # if both None
         raise error.MultisetError
         
      self._psList = [] # a list of pitch objects
      for value in self._psRealSrc: # must be psReal values
         self._psList.append(pitchTools.Pitch(value, 'psReal'))
      # store cardinality
      # removed as redundant; use get
      # self.card = self._scTriple[0]

   #------------------------------------------------------------------------||--
   # data representation
   def _reprListData(self, listData, outer=1):
      msgList = []
      for data in listData:
         if not drawer.isStr(data):
            msgList.append(str(data))
         else:
            msgList.append(data)
      msg = ','.join(msgList) # should be a list of strings
      if outer:
         return '(%s)' % msg
      else:
         return msg


   def repr(self, type='psReal', outer=1):
      """displays a Multiset; outer determines if paranethesis are incl
      """
      # first look for representations that are lists of other data
      if type in ('psReal', 'psName', 'pc', 'midi', 'pch', 'fq'):
         msgList = []
         for pitch in self._psList:
            str = pitch.repr(type)
            msgList.append(str)
         return self._reprListData(msgList, outer)
      # representations that are single values
      elif type == 'dur': # not really needed
         return '%s' % self.dur

      elif type == 'sc':
         # scToStr method should be moved inside this class
         return scToStr(self._scTriple)

      elif type == 'normal':
         normalList = self.scObj.pcs(self._scTriple)
         return self._reprListData(normalList, outer)

      elif type == 'prime':
         # strip inversion info from scTriple, default gets prime
         normalList = self.scObj.pcs(self._scTriple[0:2])
         return self._reprListData(normalList, outer)

      elif type == 'icv':
         return self._reprListData(self.icv())

      elif type == 'var':
         return self._reprListData(self.var())

      elif type == 'ref': # returns a list of strings
         refDict = self.refData()
         msgLines = []
         if refDict == None:
            return None
         else:
            for key in refDict.keys():
               msgLines.append(', '.join(refDict[key]))
            return msgLines
 
      elif type == 'refNames': # return a str of ref names
         refDict = self.refData()
         if refDict == None:
            return None
         else:
            refDict = self.refData()
            if refDict.has_key('name'):
               return ', '.join(refDict['name'])
            else:
               return None

      elif type == 'tRef':
         return '%s' % self.tRef

      else:
         raise ValueError, 'bad representation format'

   def __str__(self):
      """default string representation is as 'psReal'"""
      return self.repr()
   
   #------------------------------------------------------------------------||--
   # data entry, updating
   def setDur(self, value):
      self.dur = value

   def setT(self, value):
      """for setting a tRef on load or otherwise"""
      self.tRef = value

   def __setitem__(self, key, value):
      self._psList[key] = value
      self._update()

   def __contains__(self, item):
      """item to test is a set obj"""
      if item in self._psList:
         return 1
      else:
         return 0

   def __delitem__(self, key):
      # key is in the order position
      del self._psList[key]
      self._update()

   def __getitem__(self, key):
      self._update()
      return self._psList[key]

   #------------------------------------------------------------------------||--
   # data access
   def __len__(self):
      return len(self._psList)

   def _access(self, name):
      """output data in the appropriate format
      does not change internal data representation
      """
      if name == 'sc': # not an attribute of pitch objects
         return self._scTriple
      elif name == 'dur': # not an attribute of pitch objects
         return self.dur
      elif name == 'normal': # not an attribute of pitch objects
         return self.scObj.pcs(self._scTriple)
      elif name == 'mason':
         pcList =  []
         for pitch in self._psList:
            pcList.append(pitch.get('pc'))
         return psSetToMason(pcList) # returns an int
      elif name == 'card': # get cardinality
         return self._scTriple[0]
      dataList = []
      for pitch in self._psList:
         dataList.append(pitch.get(name))
      return tuple(dataList)

#    def __getattr__(self, name):
#       """this method of data access should no longer be used"""
#       if name not in self.forms:
#          #print 'Multiset: invalid request for', name
#          raise AttributeError
#       return self._access(name) # convert to appropriate data and return

   def get(self, name):
      if name not in self.forms:
         raise ValueError, 'bad format requested'
      return self._access(name) # convert to appropriate data and return

   # sc analysis measures
   def var(self):
      return self.scObj.var(self._scTriple)
   def icv(self):
      return self.scObj.icv(self._scTriple)
   def cv(self, n):
      return self.scObj.cv(self._scTriple, n)
   def xv(self, n):
      return self.scObj.xv(self._scTriple, n)

   def z(self): # returns none if no data
      return self.scObj.zData(self._scTriple)
   def zObj(self):
      """return a new set object for the z related set"""
      if self.z() != None:
         return Multiset(None, self.z(), self.scObj)
      else:
         return None

   def refData(self):
      return self.scObj.refData(self._scTriple)

   def superSet(self, setRange='all', tniMode=0):
      """returns raw triples w/ searchResults, valueDict
      if match is given, looks for scTriple to match, returns truth
      else returns list of tuples"""
      searchResults, valueDict = self.scObj.findSuperSets(self._scTriple, 
                                                         'all', tniMode)
      return searchResults, valueDict

   def rawData(self, key):
      """raw data via key"""
      return self.scObj.rawSetData(self._scTriple[0], self._scTriple[1],
                                   self._scTriple[2], key)

   #------------------------------------------------------------------------||--
   # data transformations

   def _update(self):
      """update scTriple in the case that pitches have chaned,
      via inversion or replacement
      """
      normData = findNormalT(self._access('psReal'))
      if normData == None: # an erro has happend
         print 'SC.py: _update: problem w/', self._access('psReal')
         raise error.MultisetError
      self._scTriple, self.tRef = normData
      # this was removed as redundant
      #self.card = self._scTriple[0]

   def t(self, value):
      """transpose each pitch objectin pitch space
      """
      for pitch in self._psList:
         pitch.t(value)
      self.tRef = self.tRef + value # update transpositon counter

   def tMod(self, value):
      """trasnpose w/in modulus, retain octave"""
      for pitch in self._psList:
         pitch.tMod(value)
      self.tRef = self.tRef + value # update transpositon counter

   def i(self, axis=None):
      """ps inversions, value is the axis
      value can be used to shift inversion"""
      if axis == None: # shift around the first note in the series
         axis = self._psList[0].get('psReal')
      for pitch in self._psList:
         pitch.i(axis)
      self._update() # sc may have changed

   def iMod(self, axis=0):
      """inversions, w/in moduls, retain octave
      axis can be a floating point value like 1.5 for certain inversions"""
      for pitch in self._psList:
         pitch.iMod(axis)
      self._update() # sc may have changed

   def retro(self):
      self._psList.reverse()

   def slice(self):
      pass
   
   def rotate(self, newZero):
      """rotate multiset
      note: this is a rotation in place; this does not take register
      into account, only pitch order"""
      if newZero == 0: return 
      
      psLen = len(self._psList)

      if newZero > 0: # map as positive mod
         newZero = newZero % psLen
      if newZero < 0: # map as negative mod
         newZero = newZero % -psLen

      if newZero > 0: # map as positive mod
         self._psList = (self._psList[newZero:psLen] + 
                           self._psList[0:newZero])
      if newZero < 0: # map as negative mod
         self._psList = (self._psList[psLen+newZero:psLen] + 
                           self._psList[0:psLen+newZero])


   def rotateOctave(self, newZero):
      """rotate multiset
      assume that new zero is the lowest pitch in the set
      transpose pitches as necessary
      """
      if newZero == 0: return 
      psLen = len(self._psList)
      if psLen == 0: return

      self.rotate(newZero) # do standard rotatioin

      # the first pitch should always be the lowest pitch
      if newZero > 0: # map as positive mod
         for i in range(1, psLen): # must be greater than 1
            while self._psList[i].get('psReal') < self._psList[0].get('psReal'):
               self._psList[i].t(12) # transpose up one octave
      # take new first pitch down an octave
      # the first pitch should then always be the lowest pitch
      if newZero < 0: # map as positive mod
         self._psList[0].t(-12)
         for i in range(1, psLen): # must be greater than 1
            # continue to check for pitches below the first pitch
            while self._psList[i].get('psReal') < self._psList[0].get('psReal'):
               self._psList[i].t(12) # transpose up one octave

   def spaceOctave(self, shift=1):
      """simple method of spacing pitches be increasing or decreasing octaves
      does not take into account existing octave positions"""
      if shift == 0: return # no change
      for i in range(len(self._psList)):
         # for each pitch in the set, add an additional octave shift
         # for each new pitch, an additional shift unit of octaves is added
         self._psList[i].t(12 * shift * i) # shift may be negatives


   def copy(self):
      obj = Multiset(self._access('psReal'), self._scTriple, self.scObj)
      obj.dur = copy.deepcopy(self.dur)
      obj.tRef = copy.deepcopy(self.tRef)
      return obj

#-----------------------------------------------------------------||||||||||||--
class MultisetFactory:
   """object to handle getting a set from a user
   sortif a MultiSet factory, for producing objects
   """
   def __init__(self):
      pass
      # scObj provided w/ call

   def _parseSetInputType(self, usrStr, termObj):
      """determine which set input is being provided by user
      termObj may be None; if not interactive, not allow import
      """
      usrStr = drawer.strScrub(usrStr, 'L')
      for char in usrStr:
         if char in ['@','&', '|',]: # removed ,'(',')'
            return 'sieve'
      # if a complete user string, get a file dialog
      if usrStr in ['file', 'import', 'spectrum']:
         if not termObj.interact: return None # cant import w/ not interactive
         return 'import'
      if usrStr.find('.txt') >= 0: # assume its a file path
         return 'txt' # import a spectrum
      if usrStr.find('m') >= 0:
         return 'midi'
      if usrStr.find('hz') >= 0 or usrStr.find('fq') >= 0:
         return 'fq'
      # the first character of a set class must always be a number
      # 10, 11, and 12 should be in this list, but requires two characters
      # there must be a dash (not leading), no comas, no periods, 
      if (usrStr[0] in ['1','2','3','4','5','6','7','8','9',] and
         usrStr.find('-') != -1 and usrStr.find(',') == -1 and
         usrStr.find('.') == -1 and usrStr[0] != '-'):
         # no other characters should be in this
         return 'forte'
      else:
         for char in usrStr.lower(): # check if it has characters
            if char in pitchTools.REFdiaNameToPc.keys():
               return 'psName'
         return 'psReal' # assume pset numbers

   def _parseForte(self, usrStr):
      """decifer a user-entered forte value"""
      #usrStr = self._scrubUsrStr(usrStr)
      #true if string has dash, no commas, no periods: is forte 
      scFound = None
      if usrStr.find('b') != -1:
         inv = -1
         usrStr = usrStr.replace('b', ' ')
      else:
         inv = 1 #this value may not be correct, is checked later on
         usrStr = usrStr.replace('a', ' ')
      usrStr = usrStr.replace('-', ' , ')    #replace dash with comma
      try:
         rawForte = eval(usrStr)
      except (NameError, SyntaxError):
         raise error.MultisetError
      if rawForte[0] < 1 or rawForte[0] > 12:
         raise error.MultisetError
      elif rawForte[1] > TNIMAX[rawForte[0]]:
         raise error.MultisetError
      else: # successfil asignment
         scFound = self.scObj.forteToSc(rawForte[0], rawForte[1], inv)
      return scFound

   def _parsePsName(self, usrStr):
      """convert a list of pitch names to a ps
      middle c == c4 == midi 60 == 0
      """
      #usrStr = self._scrubUsrStr(usrStr)
      usrList = drawer.strToListFlat(usrStr, 'L')
      psList = []
      for elem in usrList: # may be int or float
         elem = drawer.strScrub(elem)
         if elem == '': continue
         elif elem[0] not in pitchTools.REFdiaNameToPc.keys():
            continue
         else: # this should never raise an error
            psList.append(pitchTools.psNameToPs(elem))
      return psList
      
   def _parseMidi(self, usrStr):
      """conver midi values to psInt values"""
      usrStr = drawer.strStripAlpha(usrStr)
      usrList = drawer.strToListFlat(usrStr, 'L')
      #usrList = usrStr.split(',')
      psList = []
      for elem in usrList: # may be int or float
         elem = drawer.strToNum(elem.strip(), 'num')
         if elem == None: continue
         else: psList.append(pitchTools.midiToPs(elem))
      return psList
      
   def _parseFq(self, usrStr):
      """conver midi values to psInt values"""
      usrStr = drawer.strStripAlpha(usrStr)
      usrList = drawer.strToListFlat(usrStr, 'L')
      #usrList = usrStr.split(',')
      psList = []
      for elem in usrList: # may be int or float
         elem = drawer.strToNum(elem.strip(), 'num')
         if elem == None: continue
         else: psList.append(pitchTools.fqToPs(elem))
      return psList
      
   def _parsePsReal(self, usrStr):
      """process a usr string entered as a list psReals"""
      usrList = drawer.strToListFlat(usrStr, 'L')
      psList = []
      for elem in usrList: # may be int or float
         elem = drawer.strToNum(elem.strip(), 'num')
         if elem == None: continue
         else: psList.append(elem)
      return psList

   def _parseSieve(self, usrStr):
      try:
         sieveObj = sieve.SievePitch(usrStr)
         psSet = sieveObj()
      except (SyntaxError, ValueError, TypeError, 
              KeyError, error.PitchSyntaxError):
         raise error.MultisetError
      if psSet == []: # no values in this seive segment
         raise error.MultisetError
      return psSet
      
   def _parseTxt(self, usrStr, count=None):
      """convert a text file commulative spectrum"""
      # usrstr is a file path
      try:
         specObj = spectral.SpectrumData(usrStr)
         psSet = specObj.getPitch('psReal', count)
      except (ValueError, IOError):
         raise error.MultisetError
      if psSet == []: # no values in this seive segment
         raise error.MultisetError
      return psSet
      
   def _getCount(self, termObj):
      """get number of pitches to read interactively"""
      query = 'number of pitches?'
      while 1:
         usrStr = dialog.askStr(query, termObj)
         if usrStr == None: return None
         num = drawer.strToNum(usrStr, 'int')
         if num != None and num != 0: return num
         else:
            dialog.msgOut(('%senter a positive or negative integer.\n' % 
               lang.TAB), termObj)         
      
      
   def _makeObj(self, ao=None, read=None, scObj=None):
      """ returns sc, pcset, trans from 0=C, and inv
      read arg allows non-interactive use: provide data as arg
      can be used to replace calls to getSet
      pass an ao to get references and termObj
      """
      if ao != None:
         termObj = ao.termObj
         dlgVisMet = ao.external.getPref('athena', 'dlgVisualMethod')
         fpLastDir = ao.fpLastDir
      else: # get defaults
         termObj = None
         dlgVisMet = 'txt'
         fpLastDir = ''
      if scObj == None:
         scObj = SetClass()
      self.scObj = scObj
      
      attempts = 0
      usrStrType = None # not yet known what format user provided
      while 1:
         if read != None: # method must return result, not interactive
            if attempts > 0: return None # dont run more than once when reading
            usrStr = read # assign to usrStr for parsing
         else:
            usrStr = dialog.askStr(lang.msgSCgetSet, termObj)
            if usrStr == None: return None
         attempts = attempts + 1
         usrStrType = self._parseSetInputType(usrStr, termObj)
         # may get one or the other of these as input values
         scFound = None
         psSet = None
         try:
            if usrStrType == 'forte':
               scFound = self._parseForte(usrStr)
            elif usrStrType == 'psName':
               psSet = self._parsePsName(usrStr)
            elif usrStrType == 'psReal':
               psSet = self._parsePsReal(usrStr)
            elif usrStrType == 'sieve':
               psSet = self._parseSieve(usrStr)
            elif usrStrType == 'midi':
               psSet = self._parseMidi(usrStr)
            elif usrStrType == 'fq':
               psSet = self._parseFq(usrStr)
            elif usrStrType == 'txt':
               psSet = self._parseTxt(usrStr)
            # import will get a file dialog
            elif usrStrType == 'import':
               msg, ok = dialog.promptGetFile(lang.msgSCgetAudacity, 
                                  fpLastDir, 'file', dlgVisMet, termObj)
               count = self._getCount(termObj) # count may be equal to None
               # call parse text after getting file path
               if ok: psSet = self._parseTxt(msg, count)
               else: return None # cancel
            else: return None
         except error.MultisetError:
            dialog.msgOut(lang.msgSCnoSuchSet, termObj)
            continue
         try:
            obj = Multiset(psSet, scFound, self.scObj)
         except error.MultisetError:
            return None # will be understood as error

         if read == None: # dont check response
            sc = obj.repr('sc')
            ps = obj.repr('psName')
            query = lang.TAB + 'SC %s as %s? ' % (sc, ps)
            ok = dialog.askYesNoCancel(query, 1, termObj)            
            if ok != -1 and ok != 1:  continue  # return to top
            elif ok == -1: return None # destroy obj  
         return obj

   def __call__(self, termObj=None, read=None, scObj=None):
      return self._makeObj(termObj, read, scObj)

   def getRange(self, setRange='all', tni=0, scObj=None):
      """return a list w/ all sets returned as objects
      scObj os required for opperation
      """
      if scObj == None:
         scObj = SetClass()
      self.scObj = scObj

      objList = []
      for scTriple in self.scObj.getAllScTriples(setRange, tni):
         objList.append(Multiset(None, scTriple, self.scObj))
      return objList

   def getAllZ(self, setRange='all', tni=0, scObj=None):
      """return a list of all Z set objects
      scObj required"""
      if scObj == None:
         scObj = SetClass()
      self.scObj = scObj

      objList = []
      for scTriple in self.scObj.findAllZ(setRange, tni):
         objList.append(Multiset(None, scTriple, self.scObj))
      return objList

   def getAllSuperset(self, searchSetObj, setRange='all', tni=0, scObj=None):
      """return a list of all set objects that match search
      scObj required"""
      if scObj == None:
         scObj = SetClass()
      self.scObj = scObj

      searchResults, valueDict = searchSetObj.superSet(setRange, 0)
      objList = [] # transform search results into objects
      for scTriple in searchResults:
         objList.append(Multiset(None, scTriple, self.scObj))
      return objList, valueDict


   def getRef(self, searchStr, refType, setRange='all', tni=0, scObj=None):
      """return a list of all set objects that match search
      scObj required"""
      if scObj == None:
         scObj = SetClass()
      self.scObj = scObj

      objList = []
      resultList = self.scObj.findRef(searchStr, refType, setRange, tni)
      if resultList != None:
         for scTriple in resultList:
            objList.append(Multiset(None, scTriple, self.scObj))
      return objList # ordered list by incidence



#-----------------------------------------------------------------||||||||||||--
# no longer used
# def getSet(termObj=None, read=None, scObj=None):
#    """functional interface for object creation"""
#    # emulates old getSet method of SC
#    interObj = MultisetFactory()
#    obj = interObj(termObj, read, scObj)
#    if obj == None: # error
#       return None
#    else:
#       return obj.get('sc'), obj.get('psReal'), obj.tRef

# no long used, and collides w/ baseTexture method
# def getMultiset(termObj=None, read=None, scObj=None):
#    """functional interface for object creation"""
#    # emulates old getSet method of SC
#    interObj = MultisetFactory()
#    return interObj(termObj, read, scObj) # may be None on error


#-----------------------------------------------------------------||||||||||||--

def getPitch(termObj=None, read=None):
   while 1:
      if read != None:
         usrStr = read
      else:
         usrStr = dialog.askStr("enter a pitch or note name:", termObj)
         if usrStr == None:
            return None
      usrStr = usrStr.lower() # make sure lower case
      try:
         obj = pitchTools.Pitch(usrStr)
      except error.PitchSyntaxError: 
         if read != None:  
            return None # failure
         dialog.msgOut('%sno such pitch exists.\n' % lang.TAB, termObj)
         continue
      return obj

#-----------------------------------------------------------------||||||||||||--



class Test:
   def __init__(self):
      self.testMultisetData()

   def testMultisetData(self):
      demo = ((2,-5,3,5),(3.06,4.25,8.002),(34,),
               (7,9,4,5,6,4,3,2,4,5,3,3,3))
      for set in demo:
         obj = Multiset(set)
         print '\n', obj, len(obj), obj.get('card')
         for form in obj.forms:
            print form, obj.repr(form)
            print getattr(obj, form)

   def testMultisetTrans(self):
      pass
   
if __name__ == '__main__':
   Test()


www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.