#!/usr/bin/perl -w our $self = $0; $self =~ s,.*/,,; our $root = 0; our $flat2 = 1; our $second = 2; our $flat3 = 3; our $third = 4; our $fourth = 5; our $flat5 = 6; our $fifth = 7; our $flat6 = $sharp5 = 8; our $sixth = 9; our $flat7 = 10; our $seventh = 11; our $octave = 12; our $ninth = $octave + $second; our $tenth = $octave + $third; our $eleventh = $octave + $fourth; our $thirteenth = $octave + $sixth; our @majorscale = (0, 2, 4, 5, 7, 9, 11); our @minorscale = (0, 2, 3, 5, 7, 8, 10); our @major_chord_types = (qw/maj min min maj maj min dim/); our @intervalnames = ( qw/r b2 2 b3 3 4 b5 5 b6 6 b7 7/ ); # TODO: lots more chords # also, try to parse the notation instead of having a fixed table. # also also, this is the only place in the program where a ninth is # really a ninth (14 semitones up) instead of being reduced to a 2nd # (2 semitones up). # btw, "diminished" means "diminished triad", no 7th (older jazz books # used "diminished" to mean "diminished triad plus 7th", which we're # calling "diminished 7th" here) our %shapes = ( 5 => [ $root, $fifth ], no5 => [ $root, $third ], maj => [ $root, $third, $fifth ], min => [ $root, $flat3, $fifth ], 'min(no5)' => [ $root, $flat3 ], 7 => [ $root, $third, $fifth, $flat7 ], maj7 => [ $root, $third, $fifth, $seventh ], min7 => [ $root, $flat3, $fifth, $flat7 ], sus2 => [ $root, $second, $fifth ], sus4 => [ $root, $fourth, $fifth ], add9 => [ $root, $third, $fifth, $ninth ], 9 => [ $root, $third, $fifth, $seventh, $ninth ], 'min/maj7' => [ $root, $flat3, $fifth, $seventh ], aug => [ $root, $third, $sharp5 ], aug7 => [ $root, $third, $sharp5, $flat7 ], dim => [ $root, $flat3, $flat5 ], dim7 => [ $root, $flat3, $flat5, $sixth ], adim => [ $root, $flat3, $flat5, $flat7 ], 'dim(no7)' => [ $root, $flat3, $flat5 ], 'dim(no3)' => [ $root, $flat5 ], 'dim7(no3)' => [ $root, $flat5, $flat7 ], 6 => [ $root, $third, $fifth, $sixth ], min6 => [ $root, $flat3, $fifth, $sixth ], '6/9' => [ $root, $third, $fifth, $sixth, $ninth ], 'min6/9' => [ $root, $flat3, $fifth, $sixth, $ninth ], 'maj7(no3)' => [ $root, $fifth, $seventh ], 'maj7(no5)' => [ $root, $third, $seventh ], '7(no3)' => [ $root, $fifth, $flat7 ], '7(no5)' => [ $root, $third, $flat7 ], 'm7(no5)' => [ $root, $flat3, $flat7 ], '7#9' => [ $root, $third, $fifth, $flat7, $flat3 ], '7#9(no5)' => [ $root, $third, $flat7, $flat3 ], 'm/add11' => [ $root, $flat3, $fifth, $eleventh ], 'add11' => [ $root, $third, $fifth, $eleventh ], '11' => [ $root, $third, $fifth, $flat7, $eleventh ], 'min11' => [ $root, $flat3, $fifth, $flat7, $eleventh ], 'm/add13' => [ $root, $flat3, $fifth, $thirteenth ], 'add13' => [ $root, $third, $fifth, $thirteenth ], '13' => [ $root, $third, $fifth, $flat7, $eleventh, $thirteenth ], 'min13' => [ $root, $flat3, $fifth, $flat7, $eleventh, $thirteenth ], ); our @sharpnames = ( 'c', 'c#', 'd', 'd#', 'e', 'f', 'f#', 'g', 'g#', 'a', 'a#', 'b' ); our @flatnames = ( 'c', 'db', 'd', 'eb', 'e', 'f', 'gb', 'g', 'ab', 'a', 'bb', 'b' ); our $notenames = \@sharpnames; our @sharpkeys = (0, 7, 2, 9, 4, 11); our @keysigs = ( 'nat', '5b', '2#', '3b', '4#', '1b', '6b', '1#', '4b', '3#', '2b', '5#'); our $transpose = 0; our @openstrings; our $fretpos = 0; our $fretspan = 3; our $allowpartial = 0; # set with -p our $allowholes = 1; # set with -l our %notevals = ( 'c' => 0, 'c#' => 1, 'db' => 1, 'd' => 2, 'd#' => 3, 'eb' => 3, 'e' => 4, 'f' => 5, 'f#' => 6, 'gb' => 6, 'g' => 7, 'g#' => 8, 'ab' => 8, 'a' => 9, 'a#' => 10, 'bb' => 10, 'b' => 11 ); our @chordints = (qw/I bII II bIII III IV bV V bVI VI bVII VIII/); our @keynotes = ( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ); our @chords = (); our $key; # undef = guess our $forceroot = 0; # set with -r sub fix_note { my $note = shift; $note %= 12; return $note; } sub get_note_name { return ucfirst($$notenames[fix_note($_[0])]); } sub name_to_note { my $note = lc($_[0]); return $notevals{$note}; } sub add_interval { return fix_note($_[0] + $_[1]); } sub get_interval { return fix_note($_[0] - $_[1]); } sub determine_chord { my $root = shift; my @notes = @_; for my $name (sort keys %shapes) { my $shape = $shapes{$name}; my $rootname = $$notenames[$root]; my @trynotes; for(@$shape) { push @trynotes, add_interval($root, $_); } @trynotes = sort { $a <=> $b } @trynotes; if(@trynotes != @notes) { #warn "$rootname$name: extra notes\n"; } elsif(@trynotes < @notes) { # warn "$rootname$name: missing notes\n"; } else { # warn "$rootname$name: same # of notes\n"; my $match = 1; for(my $i=0; $i<@notes; $i++) { #warn $notes[$i] . ", " . $trynotes[$i]; $match = 0 if $notes[$i] != $trynotes[$i]; } return $rootname . $name if $match; } } return undef; } sub discover_chord { my @frets = split /[^x\d]/i, $_[0]; my %notes; my $root; # undef = try them all for(my $string = 0; $string < @openstrings; $string++) { next if (not defined $frets[$string]) || ($frets[$string] =~ /x/i); my $note = ($openstrings[$string] + $frets[$string]) % 12; if($forceroot && not defined $root) { $root = $note; } $notes{$note}++; } my @notes = sort { $a <=> $b } keys %notes; print "discover_chord(): notes are "; print "$$notenames[$_] " for @notes; print "\n"; #warn "@notes"; if(defined $root) { return determine_chord($root, @notes); } else { for(@notes) { $ret = determine_chord($_, @notes); return $ret if $ret; } } return undef; } sub parse_chord { my $chord = lc(shift); my $fingering; my @frets; parse_tuning($tunings{std}) unless @openstrings; # tablature input instead of chord symbol? if($chord =~ /^[x\d]/) { @frets = split /[^x\d]/i, $chord; my $gotchord = discover_chord($chord); if(!$gotchord) { warn "$self: can't figure out chord name for fingering $chord\n"; return 0; } $chord = $gotchord; } # alternate notation $chord =~ s/\+/aug/; $chord =~ s/\-/dim/; # rudimentary support for english (e.g. "A minor 7") $chord =~ s/flat/b/gi; $chord =~ s/sharp/#/gi; $chord =~ s/minor/m/gi; $chord =~ s/major/maj/gi; $chord =~ s/\s//g; my ($note, $type) = ($chord =~ /^([a-g][#b]?)(.*)?/); unless($note) { warn "$self: Invalid chord $chord\n"; return 0; } $type = "maj" unless $type; $type =~ s/^m(#|b|\d|$)/min$1/; my $shape = $shapes{$type}; unless($shape) { warn "$self: Unknown chord type $type\n"; return 0; } my $val = $notevals{$note}; unless(defined $val) { warn "$self: Unknown note $note\n"; return 0; } $val = ($val + $transpose) % 12; $note = $$notenames[$val]; my @notes; for(@$shape) { my $n = ($val + $_) % 12; $keynotes[$n]++; } my $parsed = [ $val, $note, $type ]; if(@frets) { for(@frets) { $_ = undef if $_ =~ /x/i; } $fingering = render_fingering($val, \@frets); } else { for(my $startfret = $fretpos; $startfret < $fretpos + 12; $startfret++) { $fingering = find_guitar_chord($startfret, $parsed); last if $fingering; } } push @$parsed, $fingering if $fingering; push @chords, $parsed; return 1; } sub dump_chord_shapes { for(sort keys %shapes) { my $shape = $shapes{$_}; print "$_: "; for(@$shape) { print $intervalnames[$_ % 12] . " "; } print "\n"; } } sub print_chord { my $c = shift; my ($val, $note, $type, $fingering) = @$c; my $shape = $shapes{$type}; $type =~ s/maj$//; # in output, say A, not Amaj my @notes; for(@$shape) { $_ = fix_note($_); # TODO: this turns e.g. 9ths into 2nds my $n = add_interval($val, $_); push @notes, get_note_name($n) . "=" . $intervalnames[$_]; } #my $chordname = ucfirst($note) . $type; my $chordname = get_note_name($val) . $type; if(defined($key)) { my $keyint = $val - $key; $keyint += 12 if $keyint < 0; my $chordint = $chordints[$keyint]; if($type =~ /(min|dim)/) { # TODO: this heuristic is lame $chordint = lc($chordint); } $chordname .= " (" . $chordint . ")"; } print $chordname . ": " . join(" ", @notes) . "\n"; if($fingering) { print $fingering . "\n"; } else { print "(couldn't find fingering at given fret pos)\n"; } print "\n"; } sub parse_tuning { my $tuning = shift; my $offset = shift || 0; $tuning =~ s/\s//g; # allow & ignore spaces $tuning = lc $tuning; # allow uppercase die "$self: Invalid tuning '$tuning'\n" unless $tuning =~ /^[a-g#]+$/; my @notes = ($tuning =~ /([a-g][#]?)/g); die "$self: Invalid tuning '$tuning'\n" unless @notes; @openstrings = (); my @names; for(@notes) { my $note = fix_note(add_interval($notevals{$_}, $offset)); push @openstrings, $note; push @names, get_note_name($note); } print "Tuning: " . join(" ", @names) . "\n"; } # Tuning list isn't really meant to be exhaustive, but should include # plenty of variants... our @tuning_list = ( [ 'std', 'eadgbe', 'Standard Guitar [default]' ], [ 'eb', 'd#g#c#f#a#d#', 'Guitar 1/2 step flat (Eb)' ], [ 'd', 'dgcfad', 'Guitar 1 step flat (D)' ], [ 'c#', 'c#f#beg#c#', 'Guitar 1 1/2 steps flat (C#)' ], [ 'c', 'cfa#d#gc', 'Guitar 2 steps flat (C)' ], [ 'od', 'dadf#ad', 'Open D' ], [ 'og', 'dgdgbd', 'Open G' ], [ 'oe', 'ebeg#be', 'Open E' ], [ 'oeb', 'd#a#d#ga#d#', 'Open Eb' ], [ 'oa', 'eac#eae', 'Open A' ], [ 'dd', 'dadgbe', 'Drop D' ], [ 'dc#', 'c#g#c#f#a#d#', 'Drop C#' ], [ 'dc', 'cgcfad', 'Drop C' ], [ 'nst', 'cgdaeg', 'Fripp\'s New Standard Tuning' ], [ 'b', 'eadg', 'Bass (4-string)' ], [ '5', 'beadg', 'Bass (5-string)' ], [ '6', 'beadgc', 'Bass (6-string)' ], [ '6b', 'beadgb', 'Bass (6-string alt)' ], [ 'r', 'beadf#b', 'Baritone Guitar or alt 6-string bass' ], [ '7', 'beadgbe', '7-string Guitar (low B)' ], [ '7h', 'eadgbea', '7-string Guitar (high A)' ], [ '7d', 'aeadgbe', '7-string Guitar (drop A)' ], [ 't', 'gcfa#de', 'Terz Guitar' ], [ 'm', 'gdae', 'Mandolin' ], [ 'l', 'eadf#be', 'Lute' ], [ 'h', 'adgbe', 'Mexican Vihuela' ], [ 'cv', 'cgda', 'Cello or Viola' ], [ 'v', 'gdae', 'Violin' ], [ 'vs', 'adae', 'Violin (scordatura)' ], [ 'u', 'gcea', 'Ukulele' ], [ 'ub', 'dgbe', 'Baritone Ukulele' ], ); our %tunings; sub tuning_help { for(@tuning_list) { my ($tag, $notes, $name) = @$_; printf("-t%-4s %s (same as -t%s)\n", $tag, $name, $notes); } exit 0; } sub usage { warn < [[chordopts] ] ... Chords can be either standard notation (e.g. A, Bm, C#maj7), or comma- separated fret positions, low to high, (e.g. 3,2,0,0,3,3 is a G in standard guitar tuning). Use x to indicate strings not played (e.g. x,x,0,2,3,2 is a commonly-used D chord). Global Options: -t Set tuning, low string to high. Default is eadgbe. Instrument is assumed to have as many strings as there are notes. Do not specify notes as flats (because the letter b is used for both the b note and the flat symbol). Use e.g. d# instead of eb. -t Set one of the built-in tunings. Use -th or -thelp for list. -x Transpose x semitones (x is positive or negative integer) -D Dump all known chord types -k Set the key for all chords (default is to guess key) Per-Chord Options: Once set, these stay in effect for subsequent chords. -b Print flat note names (e.g. Db instead of C#) -s Print sharp note names (e.g. C# instead of Db) [default] -f Place chord at fret position x (x=0 default, means open strings). (Actually, this is the minimum position: if no useful notes are found at x, we try x+1, x+2, ..., x+12) -s Maximum fret span. Default is 3. -v Find variant chord voicing. Doesn't always do anything useful. (for example, this gives 3,2,0,0,3,3 instead of 3,2,0,0,0,3 for an open G chord in standard tuning) -n Find normal voicing (default), turns off previous -v option. -r Force lowest bass note to be root of chord. -i Allow inversions (turns off -r). This is the default. -p Allow partial voicings (e.g. only C and E for a C major chord). Use with caution, especially with 7, 9, etc. chords (the matcher will consider a C major triad to match C7 or C9). EOF exit 0; # future options: # -l Loose matching. Accepts chord fingerings with "holes" in # the middle (e.g. 65X766 for A#). # -o Include open strings even in non-open -f positions. # -a Chart all chord voicings found for each chord (default is to # stop after first one is found). May be combined with -f, e.g. # -f5 -a means "all chord voicings from 5th fret up". # -c Capo on fret x. # -T Tablature notation instead of chord charts, for -t option. # -m[file] Generate MIDI file of chords. With [file], output to file. # Without [file], play using timidity. # -O Output type: text (default), ps, pdf, html, maybe xml someday. # -w Set output width for text output. Default: $COLUMNS from # environment, or 80 if not set. } sub has_hole { my @voicing = @_; shift @voicing while not defined $voicing[0]; pop @voicing while not defined $voicing[-1]; return 1 if @voicing == 0; # whoopsie! for(@voicing) { return 1 if not defined $_; } return 0; } # for each string, go thru the note values for frets $fretpos to # $fretpos+$fretspan, looking at each note in @vals. # TODO: de-shittify this. It was thrown together between 2 and 5AM, # and it looks like it. sub find_guitar_chord { my $startfret = shift; my $c = shift; my ($val, $note, $type) = @$c; my $shape = $shapes{$type}; my @voicing; my $string; my $fret; $note = uc $note; $type =~ s/maj$//; # in output, say A, not Amaj my @vals; for(@$shape) { my $n = ($val + $_) % 12; push @vals, $n; } @vals = sort @vals; # for e.g. 9 and add9 chords my $chordname = ucfirst($note) . $type; push @voicing, undef for @openstrings; my %foundvals; my $rootfound = 0; for($string=0; $string<@openstrings; $string++) { my ($start, $end, $step); if($findbackwards) { $start = $startfret + $fretspan; $end = $startfret - 1; $step = -1; } else { $start = $startfret; $end = $startfret + $fretspan + 1; $step = 1; } FRET: for($fret = $start; $fret != $end; $fret+=$step) { my $fretval = ($openstrings[$string] + $fret) % 12; VAL: for(@vals) { #warn "\$string==$string \$fret==$fret \$fretval==$fretval \$_==$_\n"; if($_ == $fretval) { if($forceroot && !$rootfound && $_ != $val) { next VAL; } $rootfound = 1 if $_ == $val; $voicing[$string] = $fret; $foundvals{$_}++; last FRET; } } } } if(scalar keys %foundvals < @vals) { if($allowpartial) { #warn "$note$type: didn't find all note values, return partial chord\n"; } else { #warn "$note$type: didn't find all note values, return nothing\n"; return undef; } } if(not $allowholes) { if(has_hole(@voicing)) { #warn "$note$type: has a hole\n"; return undef; } } my $r = render_fingering($val, \@voicing); return $r; } sub render_fingering { my $val = $_[0]; my @voicing = @{$_[1]}; my ($lowfret, $highfret) = (999, -1); for(@voicing) { next unless defined $_; $lowfret = $_ if $_ < $lowfret; $highfret = $_ if $_ > $highfret; } my @grid; my ($intervals, $names); for($string=0; $string<@openstrings; $string++) { my $fret = $voicing[$string]; if(not defined($fret)) { $intervals .= " "; $names .= " "; } else { my $noteval = ($openstrings[$string] + $fret) % 12; my $interval = $noteval - $val; $interval += 12 if $interval < 0; $interval %= 12; $intervals .= sprintf("%3s", $intervalnames[$interval]); $names .= sprintf("%3s", ucfirst $$notenames[$noteval]); } for($lowfret..$highfret) { my $empty = "|"; if($_ == $lowfret && $lowfret == 0) { $empty = "+"; } $grid[$_ - $lowfret][$string] = $empty; } if(defined($fret)) { $grid[$fret-$lowfret][$string] = $fret; } else { $grid[0][$string] = 'X'; } } my @rendered; for(@grid) { $_->[0] =~ s/^/ / if length($_->[0]) == 1; $_->[0] =~ s/^/ /; for($string=1; $string<@openstrings; $string++) { $_->[$string] =~ s/^/-/ if length($_->[$string]) == 1; $_->[$string] =~ s/^/-/; } push @rendered, join("", @$_); } push @rendered, $names; push @rendered, $intervals; return join("\n", @rendered); } # TODO: make this smarter. Currently it completely ignores accidentals # and takes the first match it finds. Also it gives up if it can't get # an exact match with a major scale (e.g. if given C and G chords only, # it gives up, whereas it really should guess either C or G as the key) # Probably need some kind of weighting system. sub guess_key { my ($k, $n, $ret, @possibles); for $k (0..11) { my $found = 1; for $n (0, 2, 4, 5, 7, 9, 11) { $found = 0 if not $keynotes[($k + $n) % 12]; } if($found) { push @possibles, uc($$notenames[$k]); $ret = $k unless defined $ret; } } if(@possibles == 0) { warn "$self: Can't guess key, use -k to set\n"; } elsif(@possibles > 1) { warn "$self: Possible keys: " . join(" ", @possibles) . ", guessing " . $possibles[0] . ", use -k to set\n"; } if($ret) { if(grep { $_ == $ret } @sharpkeys) { $notenames = \@sharpnames; } else { $notenames = \@flatnames; } } return $ret; } sub parse_opt { my $opt = shift; $opt =~ s/^-+//; if($opt eq 'h') { usage(); } elsif($opt eq 'b') { $notenames = \@flatnames; } elsif($opt eq 's') { $notenames = \@sharpnames; } elsif($opt =~ /^x([+-]?\d+)/) { $transpose = $1 + 0; } elsif($opt =~ /^k([a-g][#b]?)(m?)/) { my $keyopt = $1; my $minor = $2; $key = $notevals{lc $keyopt}; if(defined($key) and $minor) { $key += 3; $key %= 12; } if(defined($key)) { if(grep { $_ == $key } @sharpkeys) { $notenames = \@sharpnames; } else { $notenames = \@flatnames; } } else { warn "$self: Invalid key '$1', ignoring\n"; } } elsif($opt =~ /^f(\d+)/) { $fretpos = $1 + 0; } elsif($opt =~ /^s(\d+)/) { $fretspan = $1 + 0; } elsif($opt =~ /^t(.*)/) { my $offset = 0; my $tuning = $1; if($tuning =~ s/([-+]\d+)$//) { $offset = $1; } if($tuning =~ /^h(?:elp)?/i) { tuning_help(); } elsif($tunings{$tuning}) { parse_tuning($tunings{$tuning}, $offset); } else { parse_tuning($tuning, $offset); } } elsif($opt eq 'r') { $forceroot = 1; } elsif($opt eq 'i') { $forceroot = 0; } elsif($opt eq 'n') { $findbackwards = 0; } elsif($opt eq 'v') { $findbackwards = 1; } elsif($opt eq 'p') { $allowpartial = 1; #} elsif($opt eq 'l') { #$allowholes = 0; } elsif($opt eq 'd') { dump_chord_shapes(); } else { warn "$self: Invalid option '$_', use -h for help\n"; } } # main() for(@tuning_list) { $tunings{$_->[0]} = $_->[1]; } for(@ARGV) { if(/^-/) { parse_opt($_); } else { parse_chord($_); } } if((defined $key) && !@chords) { # got a -k but no chords, show all triad chords in this key. for(my $i = 0; $i < @majorscale; $i++) { my $note = add_interval($majorscale[$i], $key); my $name = $$notenames[$note]; my $type = $major_chord_types[$i]; parse_chord($name . $type); } } unless(@chords) { warn "$self: Found no valid chords\n"; usage(); exit 1; } unless(defined($key)) { $key = guess_key(); } for(@chords) { print_chord($_); } if(defined $key) { my $keyname = ucfirst($$notenames[$key]); my $minkey = fix_note($key - 3); my $minkeyname = ucfirst($$notenames[$minkey]); print "Key: " . $keyname . " (" . $minkeyname . "m), " . $keysigs[$key] . "\n"; print $keyname . " major scale: "; for(@majorscale) { my $note = ($key + $_) % 12; print ucfirst($$notenames[$note]) . " "; } print "\n"; print $minkeyname . " minor scale: "; for(@minorscale) { my $note = ($minkey + $_) % 12; print ucfirst($$notenames[$note]) . " "; } print "\n"; } # examples: # I know how to finger some chords, and need to know what they're called, # what notes are in them, and what key (if any) those chords imply: # chordspeller.pl 3,2,0,0,3,3 x,0,2,2,2,0 x,x,0,2,3,2 # I know the chords in a song, and need to know the key of the song: # chordspeller.pl G A D # I know the chords in a song, and need to transpose it from G to A # (up 2 semitones): # chordspeller.pl -x2 G C Am D # Any of the above can be done in any tuning other than standard guitar # tuning by way of a -t argument (see the help for the list). __END__ ---=cut-here=------- Gmin (ii) 10-10--|--|--|-10 | | | | | | |--|--|--|-11--| | | | | | | |--|-12-12--+--| | | | | | | |--|--|--|--+--| | | | | | | D G D G Bb D 5 r 5 r b3 5 ---=cut-here=------- Each chord is 20x12 with fretspan 3 and 6 strings (actually they're 19x11 with 3 columns and one row of padding). This allows a printer page that's 80x66 chars to have a 4x5 grid of them (20 chords), with some room left at the top/bottom for things like the key, scales, etc. Also a 80x24 xterm can display a 4x2 grid (8 chords). Formula: width = strings*3+1 + 3(padding) height = (fretspan+1) * 2 + 1(padding) + 1(title) + 1(notes) + 1(intervals) Compressed form: ---=cut-here=------- Gmin (ii) 10-10--|--|--|-10 |--|--|--|-11--| |--|-12-12--|--| |--|--|--|--|--| D G D G Bb D 5 r 5 r b3 5 ---=cut-here=------- height = (fretspan+1) + 1(padding) + 1(title) + 1(notes) + 1(intervals) 6string, span3, you get 20x8, or 4x8 on 80x66 page, or 4x3 on 80x25 xterm. Very compressed form: ---=cut-here= Gmin (ii) 1010-|-|-|10 |-|-|-|11-| |-|1212-|-| |-|-|-|-|-| D G D GBb D 5 r 5 rb3 5 ---=cut-here= width = strings*2 + 1(padding) height = (fretspan+1) + 1(padding) + 1(title) + 1(notes) + 1(intervals) 13x8. Gives 6x8 on paper, 6x3 on xterm, but harder to read... not so bad if all frets are single-digit though: ---=cut-here= G7 (I) +-+-0-0-0-+ |-|-|-|-|-1 |-2-|-|-|-| 3-|-|-|-|-| G B D G B G 1 3 5 1 3b7 ---=cut-here= (notice the + instead of |, in the 0 position)