# ------------------------------------------------------------------------------
# Name: tablature.py
# Purpose: Object for storing music21 information in the form of musical tablature.
#
# Authors: Luke Poeppel
#
# Copyright: Copyright © 2006-2016 Michael Scott Asato Cuthbert
# Licence: BSD, see licence.txt
# ------------------------------------------------------------------------------
'''
Music21 representation of FretNote and FretBoard objects.
TODO:
Chord from Figure
Chord from FretBoard Object with tuning.
'''
from __future__ import annotations
import unittest
from music21 import common
from music21 import exceptions21
from music21 import harmony
from music21 import pitch
from music21 import prebase
class TablatureException(exceptions21.Music21Exception):
pass
[docs]
class FretNote(prebase.ProtoM21Object):
'''
A FretNote represents a note on a Fretboard, where each string should
be fingered (or not).
>>> fn = tablature.FretNote(3, 4, 1)
>>> fn
<music21.tablature.FretNote 3rd string, 4th fret, 1st finger>
>>> fn.string
3
>>> fn.fret
4
>>> fn.fingering
1
>>> fn.displayFingerNumber
True
>>> fnUnknownFinger = tablature.FretNote(1, 1)
>>> fnUnknownFinger.fingering is None
True
'''
def __init__(
self, string: int|None = None,
fret: int|None = None,
fingering: int|None = None
):
self.string: int|None = string
self.fret: int|None = fret
self.fingering: int|None = fingering
self.displayFingerNumber: bool = True
def _reprInternal(self):
'''
Defines the representation of a FretNote object under music21 standards.
>>> fn = tablature.FretNote(4, 2, 1)
>>> fn
<music21.tablature.FretNote 4th string, 2nd fret, 1st finger>
>>> fn2 = tablature.FretNote(3, 2)
>>> fn2
<music21.tablature.FretNote 3rd string, 2nd fret>
>>> fn3 = tablature.FretNote(fret=2, fingering=3)
>>> fn3
<music21.tablature.FretNote 2nd fret, 3rd finger>
>>> emptyNote = tablature.FretNote()
>>> emptyNote
<music21.tablature.FretNote>
'''
def abbr(x):
return f'{x}{common.ordinalAbbreviation(x)}'
if self.string is not None:
stringRepr = f'{abbr(self.string)} string'
else:
stringRepr = ''
if self.fret is not None:
fretRepr = f'{abbr(self.fret)} fret'
else:
fretRepr = ''
if self.fingering is not None:
fingeringRepr = f'{abbr(self.fingering)} finger'
else:
fingeringRepr = ''
nonEmptyRepr = []
for thisRepr in stringRepr, fretRepr, fingeringRepr:
if thisRepr != '':
nonEmptyRepr.append(thisRepr)
fullRepr = ', '.join(nonEmptyRepr)
return fullRepr
[docs]
class FretBoard(prebase.ProtoM21Object):
'''
A FretBoard represents a displayed fretboard (i.e. used in chord symbols).
To be displayed, a fretboard requires a tuning system, defined by the fretted instrument
classes defined.
>>> fn1 = tablature.FretNote(string=3, fret=2, fingering=1)
>>> fn2 = tablature.FretNote(string=2, fret=3, fingering=3)
>>> fn3 = tablature.FretNote(string=1, fret=2, fingering=2)
>>> fb = tablature.FretBoard(6, fretNotes=[fn1, fn2, fn3], displayFrets=5)
>>> fb.numStrings
6
>>> fb
<music21.tablature.FretBoard 6 strings, 3 notes, 5 frets>
>>> len(fb.fretNotes)
3
>>> fb.fretNotes[0]
<music21.tablature.FretNote 3rd string, 2nd fret, 1st finger>
>>> fb.getFretNoteByString(2)
<music21.tablature.FretNote 2nd string, 3rd fret, 3rd finger>
'''
def __init__(
self, numStrings: int = 6,
fretNotes: list[FretNote]|None = None,
displayFrets: int = 4
):
self.numStrings: int = numStrings
self.fretNotes: list[FretNote] = fretNotes or []
self.displayFrets = displayFrets
self.tuning: list[pitch.Pitch] = []
def _reprInternal(self):
'''
>>> fn3 = tablature.FretNote(string=6, fret=1, fingering=1)
>>> fn2 = tablature.FretNote(string=4, fret=2, fingering=2)
>>> fn1 = tablature.FretNote(string=2, fret=4, fingering=4)
>>> fb = tablature.FretBoard(6, fretNotes=[fn3, fn2, fn1], displayFrets=4)
>>> fb
<music21.tablature.FretBoard 6 strings, 3 notes, 4 frets>
'''
return f'{self.numStrings} strings, {len(self.fretNotes)} notes, {self.displayFrets} frets'
[docs]
def fretNotesLowestFirst(self):
# noinspection PyShadowingNames
'''
Returns a list of FretNotes in lowest to highest string order.
>>> firstNote = tablature.FretNote(string=2, fret=3, fingering=4)
>>> secondNote = tablature.FretNote(string=3, fret=3, fingering=3)
>>> thirdNote = tablature.FretNote(string=1, fret=3, fingering=3)
>>> myFretBoard = tablature.FretBoard(6, fretNotes=[firstNote, secondNote, thirdNote])
>>> for thisFretNote in myFretBoard.fretNotesLowestFirst():
... print(thisFretNote)
<music21.tablature.FretNote 3rd string, 3rd fret, 3rd finger>
<music21.tablature.FretNote 2nd string, 3rd fret, 4th finger>
<music21.tablature.FretNote 1st string, 3rd fret, 3rd finger>
'''
allFretNotes = []
for stringNumber in range(self.numStrings, 0, -1):
thisFretNote = self.getFretNoteByString(stringNumber)
if thisFretNote is None:
continue
allFretNotes.append(thisFretNote)
return allFretNotes
[docs]
def getFretNoteByString(self, requestedString):
'''
Returns FretNote object on a given string or None if there are none.
>>> firstNote = tablature.FretNote(string=6, fret=3, fingering=4)
>>> secondNote = tablature.FretNote(string=2, fret=3, fingering=3)
>>> myFretBoard = tablature.FretBoard(6, fretNotes=[firstNote, secondNote])
>>> myFretBoard.getFretNoteByString(6)
<music21.tablature.FretNote 6th string, 3rd fret, 4th finger>
>>> myFretBoard.getFretNoteByString(2)
<music21.tablature.FretNote 2nd string, 3rd fret, 3rd finger>
>>> myFretBoard.getFretNoteByString(9) is None
True
'''
for thisFretNote in self.fretNotes:
if requestedString == thisFretNote.string:
return thisFretNote
return None
[docs]
def getPitches(self) -> list[None|pitch.Pitch]:
'''
Returns a list of all the pitches (or None for each) given the FretNote information. This
requires a tuning to be set.
>>> firstNote = tablature.FretNote(string=4, fret=3, fingering=3)
>>> secondNote = tablature.FretNote(string=2, fret=1, fingering=1)
>>> gfb = tablature.GuitarFretBoard(fretNotes=[firstNote, secondNote])
>>> gfb.getPitches()
[None, None, <music21.pitch.Pitch F3>, None, <music21.pitch.Pitch C4>, None]
What if the User provides an empty FretBoard?
>>> gfb2 = tablature.GuitarFretBoard(fretNotes=[])
>>> gfb2.getPitches()
[None, None, None, None, None, None]
Works for other stringed instruments, as long as the tuning is included (see below).
>>> tablature.UkeleleFretBoard().numStrings
4
>>> uke = tablature.UkeleleFretBoard(fretNotes=[firstNote, secondNote])
>>> uke.getPitches()
[<music21.pitch.Pitch B-4>, None, <music21.pitch.Pitch F4>, None]
'''
if len(self.tuning) != self.numStrings:
raise TablatureException(
'Tuning must be set first, tuned for '
f'{len(self.tuning)} notes, on a '
f'{self.numStrings} string instrument'
)
pitchList: list[pitch.Pitch|None] = [None] * self.numStrings
if not self.fretNotes:
return pitchList
for thisFretNote in self.fretNotes:
pitchListPosition = (thisFretNote.string or 0) * -1
tuningPitch = self.tuning[pitchListPosition]
tuningPitchAsPs: float = tuningPitch.ps
actualPitch: float = tuningPitchAsPs + (thisFretNote.fret or 0)
displayPitch = pitch.Pitch(actualPitch)
pitchList[pitchListPosition] = displayPitch
return pitchList
[docs]
class FirstFret:
'''
FirstFretInfo returns the information regarding the first fret utilized in a
given chord position.
'''
def __init__(self, fretNum, location='right'):
self.fretNum = fretNum
self.location = location
[docs]
class ChordWithFretBoard(harmony.ChordSymbol, FretBoard):
'''
Music21Object subclass that combines a ChordSymbol with a FretBoard.
Its tuning must be set.
>>> fn4 = tablature.FretNote(string=4, fret=0)
>>> fn3 = tablature.FretNote(string=3, fret=2, fingering=2)
>>> fn2 = tablature.FretNote(string=2, fret=3, fingering=3)
>>> fn1 = tablature.FretNote(string=1, fret=2, fingering=4)
>>> cwf = tablature.ChordWithFretBoard('Dm', fretNotes=[fn1, fn2, fn3, fn4])
'''
def __init__(self, figure=None, numStrings=6, fretNotes=None, displayFrets=4, **keywords):
harmony.ChordSymbol.__init__(self, figure=figure, **keywords)
# uncomment when self.getFretNotesFromFigure() works
# if fretNotes is None:
# fretNotes = self.getFretNotesFromFigure()
FretBoard.__init__(self,
numStrings=numStrings,
fretNotes=fretNotes,
displayFrets=displayFrets)
# -------------------------------------------------------------------------------
#
# The following classes are some fretted instruments that are commonly used in
# Tablature notation.
#
[docs]
class GuitarFretBoard(FretBoard):
'''
A six-string fretboard tuned to E A D G B E.
'''
def __init__(self, fretNotes=None, displayFrets=4):
numStrings = 6
super().__init__(numStrings, fretNotes, displayFrets)
self.tuning = [pitch.Pitch('E2'), pitch.Pitch('A2'), pitch.Pitch('D3'),
pitch.Pitch('G3'), pitch.Pitch('B3'), pitch.Pitch('E4')]
[docs]
class UkeleleFretBoard(FretBoard):
'''
A four-string fretboard tuned to G C E A.
'''
def __init__(self, fretNotes=None, displayFrets=4):
numStrings = 4
super().__init__(numStrings, fretNotes, displayFrets)
self.tuning = [pitch.Pitch('G4'), pitch.Pitch('C4'), pitch.Pitch('E4'), pitch.Pitch('A4')]
[docs]
class BassGuitarFretBoard(FretBoard):
'''
A four-string fretboard tuned to E A D G.
'''
def __init__(self, fretNotes=None, displayFrets=4):
numStrings = 4
super().__init__(numStrings, fretNotes, displayFrets)
self.tuning = [pitch.Pitch('E1'), pitch.Pitch('A1'), pitch.Pitch('D2'), pitch.Pitch('G2')]
[docs]
class MandolinFretBoard(FretBoard):
'''
A four-string fretboard tuned to G D A E.
'''
def __init__(self, fretNotes=None, displayFrets=4):
numStrings = 4
super().__init__(numStrings, fretNotes, displayFrets)
self.tuning = [pitch.Pitch('G3'), pitch.Pitch('D4'), pitch.Pitch('A4'), pitch.Pitch('E5')]
# ------------------------------------------------------------------------------
class Test(unittest.TestCase):
def testFretNoteString(self):
f = FretNote(4, 1, 2)
stringAndFretInfo = [f.string, f.fret]
self.assertEqual(stringAndFretInfo, [4, 1])
def testStupidFretNote(self):
self.assertEqual(FretNote().string, None)
def testFretNoteWeirdRepr(self):
from music21 import tablature
weirdFretNote = tablature.FretNote(6, 133)
expectedRepr = '<music21.tablature.FretNote 6th string, 133rd fret>'
self.assertEqual(repr(weirdFretNote), expectedRepr)
def testFretBoardLowestFirst(self):
fretNote1 = FretNote(1, 2, 2)
fretNote2 = FretNote(2, 1, 1)
myFretBoard = FretBoard(6, fretNotes=[fretNote1, fretNote2])
stringList = []
for thisNote in myFretBoard.fretNotesLowestFirst():
stringList.append(thisNote.string)
self.assertEqual(stringList, [2, 1])
if __name__ == '__main__':
import music21
music21.mainTest(Test)