package Music::Image::Chord; use strict; use warnings; use vars qw($VERSION); $VERSION = '0.006'; use Imager; my $standard_6 = { c => 'x32010', d => 'xxO232', e => '022100', g => '210002', a => 'x02220', f => 'xx3211', b => 'xx4442', cm => 'xx5543', dm => 'xx0231', em => '022000', gm => 'xx5333', am => 'x02210', fm => 'xx3111', bm => 'xx4432', c7 => 'x32310', d7 => 'xx0212', e7 => '020100', g7 => '320001', a7 => 'x02020', f7 => 'xx1211', b7 => 'x21202', }; my $black = Imager::Color->new(0,0,0); my $white = Imager::Color->new(255,255,255); sub new { my $class = shift; bless {}, $class; } sub bar_thickness { shift->{bar_thickness} ||= shift; } sub crop_width { shift->{crop_width} ||= shift; } sub debug { shift->{debug} ||= shift; } sub font { shift->{font} ||= shift; } sub file { shift->{file} ||= shift; } sub fret { shift->{fret} ||= shift; } sub bounds { my $self = shift; if(@_>0) { if($_[0]=~/\D/) { while(@_) { $_ = shift; if(/^w/i) { $self->{bounds}->{w} = shift; } elsif(/^h/i) { $self->{bounds}->{h} = shift; } elsif(/^xmin/i) { $self->{bounds}->{xmin} = shift; } elsif(/^xmax/i) { $self->{bounds}->{xmax} = shift; } elsif(/^ymin/i) { $self->{bounds}->{ymin} = shift; } elsif(/^ymax/i) { $self->{bounds}->{ymax} = shift; } } } else { $self->{bounds}->{w} = shift; $self->{bounds}->{h} = shift; } $self->{bounds}->{w} ||= $self->{bounds}->{xmax} - $self->{bounds}->{xmin}; $self->{bounds}->{h} ||= $self->{bounds}->{ymax} - $self->{bounds}->{ymin}; } return $self->{bounds}; } sub grid { my $self = shift; if(@_>0) { if($_[0]=~/\D/) { while(@_) { $_ = shift; if(/^w/i) { $self->{grid}->{w} = shift; } elsif(/^h/i) { $self->{grid}->{h} = shift; } elsif(/^x/i) { $self->{grid}->{x} = shift; } elsif(/^y/i) { $self->{grid}->{y} = shift; } } } else { $self->{grid}->{x} = shift; $self->{grid}->{y} = shift; $self->{grid}->{w} = shift; $self->{grid}->{h} = shift; } } return %{$self->{grid}}; } sub draw { my $self = shift; while(@_) { my ($k,$v)=(shift(),shift()); $self->{$k}=$v; } $self->{chord} ||= $standard_6->{lc $self->{name}}; if(length($self->{chord})<6) { $self->{chord} .= 'x' x 6-length($self->{chord}); } my $i = 0; for(split //,lc $self->{chord}) { if(/x/i) { push @{$self->{closed}},$i; } elsif(/[o0]/i) { push @{$self->{open}},$i; } else { push @{$self->{fingering}->[$_-1]},$i if $_>0 and $_<5; } $i++; } $self->{open_r}=($self->{grid}->{w}/2)-1; $self->{image} = Imager->new ( xsize => $self->bounds()->{w}, ysize => $self->bounds()->{h}, channels => 1, ); $self->{image}->{DEBUG}=1 if $self->debug(); $self->{image}->box ( color => $white, xmin => 0, ymin => 0, xmax => $self->bounds()->{w}, ymax => $self->bounds()->{h}, filled => 1, ); $self->_cropmarks() if $self->crop_width() > 0; $self->_grid(); $self->_open_strings(); $self->_closed_strings(); $self->_fingering(); $self->_top_label(); $self->file()=~/\.(.*)$/; $self->{type} = $1; $self->{image}->write(file=>$self->{file},type=>$self->{type}); } sub _top_label { my $self = shift; my $text = $self->{name} || $self->{labels}{top}; my $font = new Imager::Font(file => $self->font()); $self->{image}->string ( font => $font, text => $text, x => 0, y => 20, size => 20, color => $black ); } sub _cropmarks { my $self = shift; my $bounds = $self->bounds(); my $crop_width = $self->crop_width(); $self->{image}->polyline ( points=> [ [0,$crop_width], [0,0], [$crop_width,0] ], color=>$black ); $self->{image}->polyline ( points=> [ [$bounds->{w}-$crop_width-1,$bounds->{h}-1], [$bounds->{w}-1,$bounds->{h}-1], [$bounds->{w}-1,$bounds->{h}-$crop_width-1] ], color=>$black ); } sub _closed_strings { my $self = shift; my $y = $self->{grid}->{y}+$self->{grid}->{h}-$self->{grid}->{w}; for(@{$self->{closed}}) { my $x = $self->{grid}->{x}+$self->{open_r}+($self->{grid}->{w}*$_); $self->{image}->polyline ( points=> [ [$x-$self->{open_r},$y-$self->{open_r}], [$x+$self->{open_r},$y+$self->{open_r}-1], ], color => $black ); $self->{image}->polyline ( points=> [ [$x+$self->{open_r},$y-$self->{open_r}], [$x-$self->{open_r},$y+$self->{open_r}], ], color => $black ); } } sub _open_strings { my $self = shift; my $y = $self->{grid}->{y}+$self->{grid}->{h}-$self->{grid}->{w}; for(@{$self->{open}}) { my $x = $self->{grid}->{x}+$self->{open_r}+($self->{grid}->{w}*$_); $self->{image}->circle ( r => $self->{open_r}, x => $x, y => $y, color => $black, filled => 0 ); $self->{image}->circle ( r => $self->{open_r} - 1, x => $x, y => $y, color => $white, filled => 0 ); } } sub _fingering { my $self = shift; my $row = 0; my $grid_y = $self->{grid}->{y}+$self->{grid}->{h}+ ($self->{grid}->{h}/2)+($self->{open_r}/2)-$self->{open_r}; for my $fret_ref(@{$self->{fingering}}) { for(@{$fret_ref}) { $self->{image}->circle ( r => $self->{open_r}, x => $self->{grid}->{x}+$self->{open_r}+($self->{grid}->{w}*($_)), y => $grid_y+($row*($self->{grid}->{h}+2)), color => $black, filled => 0 ); } $row++; } } sub _grid { my $self = shift; for(0..5) { my $x = $self->{grid}->{x}+$self->{open_r}+($self->{grid}->{w}*$_); $self->{image}->polyline ( points=> [ [$x,$self->{grid}->{y}+$self->{grid}->{h}], [$x,$self->{grid}->{y}+$self->{grid}->{h}+($self->{grid}->{h}*5)], ], color=>$black ); } for(0..4) { my $y = $self->{grid}->{y}+($self->{grid}->{h}*($_+1)); $self->{image}->polyline ( points=> [ [$self->{grid}->{x}+$self->{open_r},$y], [$self->{grid}->{x}+$self->{open_r}+($self->{grid}->{w}*5),$y], ], color=>$black ); } if ($self->fret() == 1) { $self->{image}->box ( color => $black, xmin => $self->{grid}->{x}+$self->{open_r}, ymin => $self->{grid}->{y}+$self->{grid}->{h}-$self->bar_thickness(), xmax => $self->{grid}->{x}+$self->{open_r}+(5*$self->{grid}->{w}), ymax => $self->{grid}->{y}+$self->{grid}->{h}, filled => 1, ); } $self->{image}->polyline ( points=> [ [$self->{grid}->{x}+$self->{open_r}, $self->{grid}->{y}+$self->{grid}->{h}+($self->{grid}->{h}*5)], [$self->{grid}->{x}+$self->{open_r}+(5*$self->{grid}->{w}), $self->{grid}->{y}+$self->{grid}->{h}+($self->{grid}->{h}*5)], ], color=>$black ); } 1; __END__ =head1 NAME Music::Image::Chord - Perl extension for generating guitar tab chords =head1 SYNOPSIS use Music::Image::Chord; $image = new Music::Image::Chord(); $old_font = $image->font('/path/to/my/TrueType/font.ttf'); $old_file = $image->file('/path/to/file/to/save.png'); $image->draw(name => 'D'); # Write the actual file. =head1 DESCRIPTION B is a simple package for creating images of guitar chords in any format that the B module can produce. The object's API is as follows: =over =item B Creates and returns a new Image::Chord object. =item B Returns and optionally sets the thickness of the fret bar. Only used if on the first fret (which is the default). =item B Returns and optionally sets the image boundaries. It accepts several formats: $image->bounds($width, $height); $image->bounds(width=>$wid, height=>$hgt); $image->bounds(xmin=>$xmin, xmax=>$xmax, yMIN=>$ymin, YMax=>$ymax); =item B Returns and optionally sets the width/height of the crop marks on the image. If this value is not set or is <= 0, then crop marks will not be drawn. $image->crop_width(5); # Crop marks will be 5 pixels wide. print $image->crop_width(); # Display the current crop mark width. =item B Returns and optionally sets the Imager debugging flag. $image->debug(1); # Set debugging print $image->debug(); # Get the current debugging value =item B Returns and optionally sets the font used to render the chord's title. $image->font('/home/jgoff/Bach.ttf'); # Display in the Bach TrueType font $foo = $image->font(); # Assign $foo to the image's font =item B Returns and optionally sets the file name to save the rendered image to. It also reads the extension of the file to determine how to save the image. $image->file('/home/jgoff/chord.png'); =item B Returns and optionally sets the beginning fret in the image. Defaults to 1. $image->fret(5); print $image->fret(); # prints the current fret setting. =item B Renders the chord described into the appropriate file. Optional named parameters are: name - The name of the chord fret - Beginning fret barres - Chord barres, not implemented yet. chord - If the chord isn't represented in the list, describe it like 'xx0232'. $image->draw( name => 'D' ); # Draw a D chord =item B Returns and optionally sets the grid coordinates. X and Y are the UL corner of the grid, and w and h control (for the moment, this will change) the inter-string and inter-fret spacing. $image->grid($x,$y,$w,$h); # Set explicitly $image->grid(x=>$x,y=>$y,w=>$w,h=>$h); # Set through named parameters. =back =head1 SEE ALSO L perl(1). =head1 AUTHOR Jeffrey Goff, Ejgoff@cpan.org Inspiration by #perl =head1 COPYRIGHT AND LICENSE Copyright (C) 2003 by Jeffrey Goff This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.1 or, at your option, any later version of Perl 5 you may have available. =cut