antscloud / fretboardgtr

Python package for creating fretboard and chord diagram image in svg format
GNU Affero General Public License v3.0
112 stars 17 forks source link

RFC: Spike Implementation for display of barre / finger sets and general text elements (like titles) #31

Open ckolumbus opened 2 months ago

ckolumbus commented 2 months ago

Feature Finger element

I tried to make an implementation for displaying 'barre' elements and would like to discuss the feature interface but also the implementation approach here.

It would be great if someone can provide feedback on the style of the API and the current implementation. I'd really like to improve this in a way so that it can be merged in this repo!

This could address requests from #7, and to some extend #19.

The spike implementation can be found in my fork.

The rational for the style of implementation is, that the current element (e.g. existing chord fingerings) do not allow for multiple elements on one string. A more flexible modeling of string/fret and multi-string/fret definitions was needed IMHO. But hey, any feedback is welcome πŸ‘πŸ½ !

Impl Description

I've added a new element 'finger' which represents a finger id (just a text), a fret number and one string or a range of strings. This is drawn in the normal black rounded rectangle format.

Two new types are used for this (maybe this can be improved, but seemed sufficient for this spike):

# Tuple content
#  1 : String range or single string
#  2 : fret 
#  3 : text (e.g. finger number)
FingerPos = NewType('FingerPos', Tuple[Union[ Tuple[int,int], int], int, Optional[str]]) 
FingerSet = NewType('FingerSet', List[ FingerPos ])

The Fretboard class has been extended with the following three functions

During the implementation I found some issues with the implementation of add_note and add_single_note_from_index where the handling of horizontal and vertical layouts was not working correctly. I'll add an other issue for this but implemented a quick workaround to fix the APIs and use it for the finger implementation.

Examples

Define a finger set for G on fret 3

g_maj = [
    ((0,5), 3, "1"),
    (1, 5 , "4"),
    (2, 5 , "3"),
    (3, 4 , "2")
] 

c = { "general": { "last_fret" : 6 } }

Display G finger set

f_1 = FretBoard(config=c, vertical=False)
f_1.add_fingerset(g_maj)
f_1.export("g_maj_fingerset.svg", format="svg")

Result

g_maj_fingerset

Display G finger set with Notes

f_2 = FretBoard(config=c, vertical=False)
f_2.add_fingerset(g_maj, show_text=False)
f_2.add_notes_from_fingerset(g_maj, root="G")
f_2.export("g_maj_notes_from_fingerset.svg", format="svg")

Result

g_maj_notes_from_fingerset

Add Fingerset to scale display

c_3 = {
    "general": {
        "first_fret" : 4,
        "last_fret" : 9
    }
}
ROOT = "C"
MODE = ModeName.MAJOR_PENTATONIC

scale = ScaleFromName(root=ROOT, mode=MODE).build().get_scale_positions(max_spacing=4)[2]
c_maj_triad: FingerSet = [ ((2,4), 5) ]

f_3 = FretBoard(config=c_3, vertical=False)
f_3.add_fingerset(c_maj_triad)
f_3.add_scale(scale, ROOT)
f_3.export("scale_with_triad_fingerset.svg", format="svg")

Result

scale_with_triad_fingerset

Add single Barre finger position to scale

f_4 = FretBoard(config=c_3, vertical=False)
f_4.add_fingerpos(((2,4), 5, ""))
f_4.add_scale(scale, ROOT)
f_4.export("scale_with_fingerpos.svg", format="svg")

Result

scale_with_fingerpos

Feature Text element

The branch above also contains an implementation of a general Text element, which could be used to address #30

from fretboardgtr.elements.text import Text as FretboardText

config = { "general": { "y_start": 100 } }

t = FretboardText("Am/C Pentatonic" ,(150,50), align="left")
f = FretBoard(config=config)
f.add_element(t)

scale_with_title

antscloud commented 2 months ago

Hi ckolumbus, thank you so much for opening this issue, it's such a great job you've done here ! I've opened a PR to discuss directly about the changes in code you've done. I don't have much time lately, but i'll try to make a review as soon as I can

At first glances these changes seem to be good and seem to fit into the existing logic of the code though :)