#!/usr/local/bin/python
#-----------------------------------------------------------------||||||||||||--
# Name: generalMidi.py
# Purpose: generalMidi instrument definitions.
#
# Authors: Christopher Ariza
#
# Copyright: (c) 2001-2005 Christopher Ariza
# License: GPL
#-----------------------------------------------------------------||||||||||||--
from athenaCL.libATH.libOrc import baseOrc
from athenaCL.libATH import pitchTools
from athenaCL.libATH import drawer
_MOD = 'generalMidi.py'
#-----------------------------------------------------------------||||||||||||--
gmProgramNames = {'piano1':0, 'piano2':1, 'piano3':2,
'honkyTonkPiano':3, 'rhodesPiano':4, 'ePiano':5,
'harpsichord':6, 'clavinet':7, 'celesta':8, 'glockenspiel':9,
'musicBox':10, 'vibraphone':11, 'marimba':12, 'xylophone':13,
'tubularBells':14, 'santur':15, 'organ1':16, 'organ2':17,
'organ3':18, 'churchOrgan':19, 'reedOrgan':20,
'accordion':21, 'harmonica':22, 'bandoneon':23,
'nylonGuitar':24, 'steelGuitar':25, 'jazzGuitar':26,
'cleanGuitar':27, 'mutedGuitar':28, 'overDriveGuitar':29,
'distortonGuitar':30, 'guitarHarmonics':31, 'acousticBass':32,
'fingeredBass':33, 'pickedBass':34, 'fretlessBass':35,
'slapBass1':36, 'slapBass2':37, 'synthBass1':38,
'synthBass2':39, 'violin':40, 'viola':41, 'cello':42,
'contraBass':43, 'tremoloStrings':44, 'pizzicatoStrings':45,
'orchestralHarp':46, 'timpani':47, 'strings':48,
'slowStrings':49, 'synthStrings1':50, 'synthStrings2':51,
'choirAahs':52, 'voiceOohs':53, 'synthVox':54,
'orchestraHit':55, 'trumpet':56, 'trombone':57, 'tuba':58,
'mutedTrumpet':59, 'frenchHorn':60, 'brassSection':61,
'synthBrass1':62, 'synthBrass2':63, 'sopranoSax':64,
'altoSax':65, 'tenorSax':66, 'baritoneSax':67, 'oboe':68,
'englishHorn':69, 'bassoon':70, 'clarinet':71, 'piccolo':72,
'flute':73, 'recorder':74, 'panFlute':75, 'bottleBlow':76,
'shakuhachi':77, 'whistle':78, 'ocarina':79, 'squareWave':80,
'sawWave':81, 'synCalliope':82, 'chifferLead':83,
'charang':84, 'soloVoice':85, '5thSawWave':86, 'bassAndLead':87,
'fantasia':88, 'warmPad':89, 'polySynth':90, 'spaceVoice':91,
'bowedGlass':92, 'metalPad':93, 'haloPad':94, 'sweepPad':95,
'iceRain':96, 'soundTrack':97, 'crystal':98, 'atmosphere':99,
'brightness':100, 'goblins':101, 'echoDrops':102,
'starTheme':103, 'sitar':104, 'banjo':105, 'shamisen':106,
'koto':107, 'kalimba':108, 'bagpipe':109, 'fiddle':110,
'shanai':111, 'tinkleBell':112, 'agogoBells':113,
'steelDrums':114, 'woodBlock':115, 'taikoDrum':116,
'melodicTom1':117, 'synthDrum':118, 'reverseCymbal':119,
'guitarFretnoise':120, 'breathNoise':121, 'seaShore':122,
'birdTweet':123, 'telephoneRing':124, 'helicopterBlade':125,
'applauseNoise':126, 'gunShot':127 }
# perc on ch 10; name to midi note number
gmPercussionNames = {'acousticBassDrum':35, 'bassDrum1':36, 'sideStick':37, 'acousticSnare':38, 'handClap':39, 'electricSnare':40, 'lowFloorTom':41,
'closedHiHat':42, 'highFloorTom':43, 'pedalHiHat':44, 'lowTom':45, 'openHiHat':46, 'lowMidTom':47, 'hiMidTom':48, 'crashCymbal1':49, 'highTom':50, 'rideCymbal1':51, 'chineseCymbal':52, 'rideBell':53, 'tambourine':54, 'splashCymbal':55, 'cowBell':56, 'crashCymbal2':57, 'vibraSlap':58, 'rideCymbal2':59, 'hiBongo':60, 'lowBongo':61, 'muteHiConga':62, 'openHiConga':63, 'lowConga':64, 'highTimbale':65, 'lowTimbale':66, 'highAgogo':67, 'lowAgogo':68, 'cabasa':69, 'maracas':70, 'shortWhistle':71, 'longWhistle':72, 'shortGuiro':73, 'longGuiro':74, 'claves':75, 'hiWoodBlock':76, 'lowWoodBlock':77, 'muteCuica':78, 'openCuica':79, 'muteTriangle':80, 'openTriangle':81,
}
#-----------------------------------------------------------------||||||||||||--
# access utilities
def getPgmName(pgmNum):
pgmNum = int(pgmNum)
if pgmNum < 0: pgmNum = 0
if pgmNum > 127:
pgmName = 'unknown'
else:
for name, num in gmProgramNames.items():
if num == pgmNum:
pgmName = name
break
return pgmName
def getPgmNumber(usrStr):
if usrStr == '': return None
try:
numVal = int(usrStr)
except (ValueError, TypeError):
numVal = -1
if numVal >= 0: # its a number, try to use
return getPgmName(numVal), numVal
# its a string
usrStr = drawer.strScrub(usrStr, 'L')
scoreDict = {}
for name in gmProgramNames.keys():
nameTest = name.lower()
scoreDict[name] = nameTest.count(usrStr) # counts instances
best = ('', 0) # name, score
# go basck over items; name does have case
for name, score in scoreDict.items():
if score > best[1]:
best = (name, score)
if best[0] == '': # nothing found
return None
else:
return best[0], gmProgramNames[best[0]] # name, pgmNum
def getPercNameFromNoteName(usrStr):
"""get perc name from midi note number
nameStr can be a string (psName) or number (midiNote number)
"""
if drawer.isStr(usrStr):
psInt = pitchTools.psNameToPs(usrStr)
midiInt = pitchTools.psToMidi(psInt)
else:
midiInt = int(usrStr)
# normalize bad values
if midiInt < 35: midiInt = 35
if midiInt > 81: midiInt = 81
else:
for name, num in gmPercussionNames.items():
if num == midiInt:
percName = name
break
return percName
def getNoteNameFromPercName(percStr):
"for a given percussion name, find what note name is needed"
foundName = None
percStr = drawer.strScrub(percStr, 'L')
for name in gmPercussionNames.keys():
if percStr == name.lower():
foundName = name
break
if foundName == None: # check again for partial matches
for name in gmPercussionNames.keys():
name = name.lower()
if name.find(percStr) >= 0:
foundName = name
break
if foundName == None:
print _MOD, 'cant find:', percStr
midiInt = gmPercussionNames[foundName]
noteName = pitchTools.midiToNoteName(midiInt)
return noteName # may be None on error
#-----------------------------------------------------------------||||||||||||--
#-----------------------------------------------------------------||||||||||||--
# object models
class GeneralMidi(baseOrc.Orchestra):
"""gm midi instruments"""
def __init__(self):
baseOrc.Orchestra.__init__(self)
self.name = 'generalMidi'
self._dummyInst = InstrumentMidi()
self._instrNumbers = range(0, 128) # 127 program numbers
def instNoValid(self, iNo):
"""test if an instrument number is valid"""
if drawer.isInt(iNo) and iNo in self._instrNumbers: return 1
else: return 0
def instNoList(self, format=None):
"""return a list of instrument numbers; if
a list is not availabe, return None"""
if format == 'user':
return drawer.listToStr(self._instrNumbers)
return self._instrNumbers
def constructOrc(self, noCh=None, instList=None):
"""nothing to do here"""
return None
def getInstInfo(self, iNo=None):
"""returns a dictionary of instrNo : (Name, pNo, pInfo)
has data for all instruments
pmtrFields includes 6 default values
"""
if iNo == None:
instrList = self._instrNumbers
else:
instrList = [iNo,]
instInfoDict = {}
for number in instrList:
instInfoDict[number] = (getPgmName(number),
self._dummyInst.pmtrFields, # always 6
self._dummyInst.pmtrInfo) # always empty
return instInfoDict, instrList
def getInstPreset(self, iNo, auxNo=None):
return self._dummyInst.getPresetDict() # will returns empty dict
def getInstName(self, iNo):
return getPgmName(iNo)
def getInstAuxNo(self, iNo):
return self._dummyInst.auxNo
def getInstPmtrInfo(self, iNo, pmtrNo):
"""for specified inst, pmtrNo, return pmtr info"""
# midi inst have no additional parameter information
return 'no information available'
#------------------------------------------------------------------------||--
# mappings of psReal, amp, pan; only applied of mix mode is on
def _postMapPs(self, iNo, val): # alwasy map pitch
# pitchtools limit will automatically constrain min/max as well
# this rounds floating point values to integers
val = pitchTools.psToMidi(val, 'limit')
# always limit max values
if val < 0: val = 0
if val > 127: val = 127
return val
# pre 1.3 csoundNative files will have amps mostly in the range of
# 0 to 80, so this will work; sample insturuments, however, have
# much lower amplitudes
def _postMapAmp(self, iNo, val, orcMapMode=1): # values between 0 and 1
if orcMapMode: # optional map
# does not seem necessary to use 127; seems to peak
val = int(round(val * 124.0))
else: # take raw value as int
val = int(round(val))
# always limit
if val < 0: val = 0
if val > 127: val = 127
return val
def _postMapPan(self, iNo, val, orcMapMode=1):
if orcMapMode: # optional map
val = int(round(val * 127.0))
else: # must be an int
val = int(round(val))
# always limit
# do modulus rotation on pan values
if val < 0 or val > 127:
val = val % 127
return val
#-----------------------------------------------------------------||||||||||||--
class GeneralMidiPercussion(GeneralMidi):
"""percussion as subclass of gm
compatible w/ gm; only difference is instruments made available
this distincition is important when used in an eventMode, to select ints
not important when used in an engine to process values"""
def __init__(self):
GeneralMidi.__init__(self)
self.name = 'generalMidiPercussion'
self._dummyInst = InstrumentMidi()
# instrument numbers are the pitch values
self._instrNumbers = range(35, 82) # 35-81 program numbers
def getInstName(self, iNo):
"""over-ride from gm, as gotten from different dictionary"""
return getPercNameFromNoteName(iNo)
def getInstInfo(self, iNo=None):
"""returns a dictionary of instrNo : (Name, pNo, pInfo)
has data for all instruments
pmtrFields includes 6 default values
"""
if iNo == None:
instrList = self._instrNumbers
else:
instrList = [iNo,]
instInfoDict = {}
for number in instrList:
instInfoDict[number] = (getPercNameFromNoteName(number),
self._dummyInst.pmtrFields, # always 6
self._dummyInst.pmtrInfo) # always empty
return instInfoDict, instrList
#-----------------------------------------------------------------||||||||||||--
# one instrument object for all midi insts, general and perc
class InstrumentMidi(baseOrc.Instrument):
def __init__(self):
baseOrc.Instrument.__init__(self)
self.pmtrDefault = {} # no default for midi instruments
self.pmtrInfo = {}
self.pmtrFields = self.pmtrCountDefault
self.auxNo = 0
self.pmtrFields = self.pmtrCountDefault + self.auxNo
# pan and amp ranges are b/n 0 and 127
# for all instruments
self.postMapAmp = (0,127, 'linear')
self.postMapPan = (0,127, 'linear')
|