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;