package MIDI::Pitch;
use 5.00503;
use strict;
require Exporter;
use vars qw($VERSION @ISA @EXPORT_OK
%name2pitch_lut @pitch2name_table $base_freq);
@ISA = qw(Exporter);
@EXPORT_OK =
qw(name2pitch pitch2name freq2pitch pitch2freq basefreq name2freq freq2name findsemitone);
$VERSION = '0.7';
$base_freq = 440;
=head1 NAME
MIDI::Pitch - Converts MIDI pitches, note names and frequencies into each other
=head1 SYNOPSIS
use MIDI::Pitch qw(name2pitch pitch2name freq2pitch pitch2freq basefreq);
my $pitch = name2pitch($name);
=head1 DESCRIPTION
This module converts MIDI pitches between 0 and 127 (called 'note numbers'
in the MIDI standard) and note names into each other. The octave
numbers are based on the table found in the MIDI standard (see
L):
The MIDI specification only defines note number 60 as "Middle C", and
all other notes are relative. The absolute octave number designations
shown here are based on Middle C = C4, which is an arbitrary
assignment.
The note names are C, C/C, C, ..., followed by an octave
number from -1 to 9. Thus, the valid notes range between C and
C.
=head1 FUNCTIONS
=head2 name2pitch
my $pitch = name2pitch($name);
Converts a note name into a pitch.
=cut
%name2pitch_lut = (
'b#' => 0,
c => 0,
'c#' => 1,
'db' => 1,
d => 2,
'd#' => 3,
'eb' => 3,
e => 4,
'fb' => 4,
'e#' => 5,
f => 5,
'f#' => 6,
'gb' => 6,
g => 7,
'g#' => 8,
'ab' => 8,
a => 9,
'a#' => 10,
'bb' => 10,
b => 11,
'cb' => 11);
sub name2pitch {
my $n = shift;
return undef unless defined $n && lc($n) =~ /^([a-g][b#]?)(-?\d\d?)$/;
my $p = $name2pitch_lut{$1} + ($2 + 1) * 12;
return undef unless $p >= 0 && $p <= 127;
return $p;
}
=head2 pitch2name
my $name = pitch2name($pitch);
Converts a pitch between 0 and 127 into a note name. pitch2name returns
the lowercase version with a sharp, if necessary (e.g. it will return
'g#', not 'Ab').
=cut
@pitch2name_table =
('c', 'c#', 'd', 'd#', 'e', 'f', 'f#', 'g', 'g#', 'a', 'a#', 'b');
sub pitch2name {
my $p = shift;
return undef unless defined $p && $p =~ /^-?(\d+|\d*(\.\d+))$/;
$p = int($p + .5 * ($p <=> 0));
return undef unless $p >= 0 && $p <= 127;
return $pitch2name_table[$p % 12] . (int($p / 12) - 1);
}
=head2 freq2pitch
my $pitch = freq2pitch($440);
Converts a frequency >= 0 Hz to a pitch, using the base frequency set.
=cut
sub freq2pitch {
my $f = shift;
return undef unless defined $f && $f =~ /^(\d+|\d*(\.\d+))$/ && $f > 0;
return 69 + 12 * log($f / $base_freq) / log(2);
}
=head2 pitch2freq
my $freq = pitch2freq(69);
Converts a pitch to a frequency, using the base frequency set.
=cut
sub pitch2freq {
my $p = shift;
return undef unless defined $p && $p =~ /^-?(\d+|\d*(\.\d+))$/;
return exp((($p - 69) / 12) * log(2)) * $base_freq;
}
=head2 name2freq
my $freq = name2freq('c2');
This is just an alias for C.
=cut
sub name2freq {
return pitch2freq(name2pitch(@_));
}
=head2 freq2name
my $name = freq2name('c2');
This is just an alias for C.
=cut
sub freq2name {
return pitch2name(freq2pitch(@_));
}
=head2 findsemitone {
my $pitch = findsemitone('d#', 60);
Finds the nearest pitch that expresses the semitone given around the
pitch given. The example above would return 63, since the d# at pitch 63 is
nearer to 60 than the d# at pitch 51.
The semitone can be specified in the same format as a note name (without
the octave) or as an integer between 0 and 11.
If there are two possibilities for the nearest pitch, findsemitone returns
the lower one.
=cut
sub findsemitone {
my ($semitone, $pitch) = @_;
return undef unless defined $semitone &&
(($semitone =~ /^\d+$/
&& $semitone >= 0
&& $semitone <= 11) || exists $name2pitch_lut{$semitone});
return undef
unless defined $pitch
&& $pitch =~ /^\d+$/
&& $pitch >= 0
&& $pitch <= 127;
$semitone = $name2pitch_lut{$semitone} if exists $name2pitch_lut{$semitone};
my $m = $pitch % 12;
my $result = $pitch - $m + $semitone;
$result += 12 if ($pitch - $result > 6 && $result < 116);
$result -= 12 if ($result - $pitch > 6 && $result > 11);
return $result;
}
=head2 basefreq
my $basefreq = basefreq;
basefreq(432);
Sets/returns current base frequency for frequency/pitch conversion. The
standard base frequency set is 440 (Hz). Note that the base frequency
does not affect the pitch/name conversion.
=cut
sub basefreq {
my $f = shift;
$base_freq = $f if defined $f && $f > 0;
return $base_freq;
}
=head1 HISTORY
=over 8
=item 0.7
Added Changes file.
=item 0.6
findsemitone now also understands semitones specified as integers between 0 and 11.
Fixed bug in findsemitone.
=item 0.5
Added findsemitone function
=item 0.2
Added pitch rounding (60.49 and 59.5 will both be considered 60/'C4').
Added frequency/pitch conversion.
Added POD tests.
=item 0.1
Original version; created by h2xs 1.22 with options
-A -C -X -n MIDI::Pitch -v 0.1 -b 5.5.3
=back
=head1 SEE ALSO
L. L.
L
=head1 AUTHOR
Christian Renz, Ecrenz @ web42.comE
=head1 COPYRIGHT AND LICENSE
Copyright 2004-2005 by Christian Renz Ecrenz @ web42.comE. All Rights Reserved.
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.
=cut
1;