# This file is part of PyChoReLib.
#
# PyChoReLib is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# PyChoReLib is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with PyChoReLib; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
from Exceptions import NoMatch
from KnowledgeBasedRecognizer import KnowledgeBasedRecognizer
from Scale import Scale
from ScaleName import ScaleName
from ScaleBuilder import ScaleBuilder
from ScaleDefinitions import ScaleDefinitions
from HelperFunctions import P
import copy
ALLNOTES = [ 'a', 'b', 'c', 'd', 'e', 'f', 'g' ]
ALLNOTES.extend( ['a#', 'b#', 'c#', 'd#', 'e#', 'f#', 'g#' ] )
ALLNOTES.extend( ['ab', 'bb', 'cb', 'db', 'eb', 'fb', 'gb' ] )
ALLNOTES.extend( ['ax', 'bx', 'cx', 'dx', 'ex', 'fx', 'gx' ] )
ALLNOTES.extend( ['abb', 'bbb', 'cbb', 'dbb', 'ebb', 'fbb', 'gbb' ] )
class ScaleRecognizer(KnowledgeBasedRecognizer):
def RegisterScaleDefinitionByExample(self, AScale, AScaleName, Verbosity):
"""
Method to register a new scale type, by showing an example of such a scale.
"""
SimplifiedScale = AScale.FindCanonicForm()
if SimplifiedScale != AScale.Notes:
if Verbosity != 0:
print "*** Warning: registration of non-canonical scale", AScale.Notes, "as ", SimplifiedScale
pass
Intervals= Scale(SimplifiedScale).ToIntervalPattern()
DBKey = tuple(Intervals)
if self.KnowledgeBase.has_key(DBKey):
if Verbosity != 0:
print "*** Warning: duplicate Scale definition overrules earlier defined scale. Old: ",self.KnowledgeBase[DBKey][0].Print(),"New: ",AScaleName.Print()
RootNoteIndex = SimplifiedScale.index(AScaleName.GetRootName())
ModeRootNoteIndex = SimplifiedScale.index(AScaleName.GetModeRootName())
self.KnowledgeBase[DBKey] = tuple([AScaleName, RootNoteIndex, ModeRootNoteIndex])
def RegisterScaleSetByExample(self, ScaleType, ListOfScaleNotes, ModeNames, Verbosity):
"""
Convenience method to register a whole set of modes formed by starting AScale on successive notes.
"""
for Mode in ModeNames:
if Mode:
StartNoteIdx = ModeNames.index(Mode)
TheScale = Scale(ListOfScaleNotes[StartNoteIdx:] + ListOfScaleNotes[:StartNoteIdx])
TheScaleName = ScaleName(ListOfScaleNotes[0], ScaleType, ListOfScaleNotes[StartNoteIdx], Mode)
self.RegisterScaleDefinitionByExample(TheScale, TheScaleName, Verbosity)
def InitializeKnowledgeBase(self,Verbosity=0):
""" Teach all scale types to the system. This may take some time.
Alternatively, a generated database can be serialized to disk, and loaded on start-up
"""
P(Verbosity,"Start teach scale types. This may take some time")
for BaseScale, Notes, Modes in ScaleDefinitions:
self.RegisterScaleSetByExample(BaseScale, Notes, Modes, Verbosity)
P(Verbosity,"Done teaching")
def ScaleToKey(self, AScale):
""" Method that takes a Scale AScale, and transforms it to a key into the knowledge base """
SimplifiedScale = Scale(AScale.FindCanonicForm())
Intervals = SimplifiedScale.ToIntervalPattern()
return tuple(Intervals)
def RecognizeScale(self, AScale):
""" Method to lookup a Scale AScale in the knowledge base """
DBKey = self.ScaleToKey(AScale)
if self.KnowledgeBase.has_key(DBKey):
MatchedScale = self.KnowledgeBase[DBKey][0]
SimplifiedScale = Scale(AScale.FindCanonicForm())
RootName = SimplifiedScale.Notes[self.KnowledgeBase[DBKey][1]]
MatchedScale.SetRootName(RootName)
ModeRootName = SimplifiedScale.Notes[self.KnowledgeBase[DBKey][2]]
MatchedScale.SetModeRootName(ModeRootName)
return (MatchedScale, SimplifiedScale, RootName)
else:
raise NoMatch
# Method to find out from which scales (modes) a given chord can be
# derived.
def CreateScaleListMatchingChord(self, ListOfNotenamesInChord, AllowModes):
sb = ScaleBuilder()
found_scales = []
for n in ALLNOTES:
for s in self.KnowledgeBase:
ScaleName = self.KnowledgeBase[s][0]
#print "ScaleName=",ScaleName
RootIdx = self.KnowledgeBase[s][1]
#print "RootIdx=",RootIdx
ModeRootIdx = self.KnowledgeBase[s][2]
#print "ModeRootIdx=",ModeRootIdx
try:
ScaleNotes = sb.GetNotes(n, ScaleName.GetMode())
except NoMatch:
continue
#print ScaleNotes
if set(ListOfNotenamesInChord).issubset(set(ScaleNotes)):
s = ScaleName
s.SetRootName( ScaleNotes[RootIdx] )
s.SetModeRootName( ScaleNotes[ModeRootIdx] )
#print "Chord: ", ListOfNotenamesInChord, " ScaleNotes: ", ScaleNotes, " n = ", n, " ScaleType= ", ScaleName.GetType(), "RootIdx=", RootIdx, "ModeRootIdx=", ModeRootIdx, " ScaleName = ", ScaleName, "Match: RootName = ", ScaleNotes[RootIdx], " ModeName ", ScaleNotes[ModeRootIdx]
#print s
FirstMode = RootIdx == ModeRootIdx
if AllowModes or FirstMode:
found_scales.append(copy.copy(s))
return found_scales
def KnownScales(self):
""" Return list of known scales."""
return [self.KnowledgeBase[DBKey][0].__repr__() for DBKey in self.KnowledgeBase]
if __name__ == "__main__":
s = ScaleRecognizer()
C = ['c', 'd', 'eb', 'g' ]
print "******************************************"
for x in s.CreateScaleListMatchingChord(C, True):
print x
print "******************************************"
for y in s.CreateScaleListMatchingChord(C, False):
print y