首页 > 此perl文件该如何运行?

此perl文件该如何运行?

这个代码包是从sourceForge下载下来的,名字叫midi2chord,目的是把MIDI转化成和弦。本人可以说是几乎没怎触碰过perl。我试着运行了midi2chord.pl,但是页面是这样的,可以说是什么都没有。想问一下,这个程序是怎么输入参数(电脑上的midi文件)的?

下面是midi2chord.pl的代码,几乎什么都看不懂:( :

#!/usr/bin/perl -w
#
###############################################################################
# Analizes a given midi-file and tries to determine the chords of the music.
#
# EDITOR: Markus Kollmar, 74639 Zweiflingen, Germany
# LICENSE: GPL
# PROJECT-HOME: http://sourceforge.net/projects/midi2chord/
# TODO:
#   -Improve chord analysis and fix some possible bugs.
#   -Warning if pitched notes are given.
#   -Instead of using only the notes at a special position to determine chord,
#   use also the context (e.g. scale) and notes before or after the position.
#   -Correct note-name printing (e.g. at scale G: print "f#" instead of "ges").
# NOTES:
#   We use the term 'ticks' as a time-base of the midi data in a memory. If a
#   midi data is sent over a cable we speak of 'clicks'.
###############################################################################

use strict;
use warnings;
use MIDI;
use Midi2Abstract;
use Getopt::Std;


# Contains the algos which are requested to call:
our @Alg;
# Structure for alg0:
our %ChordPatterns;
# Contains the options which were given as parameter to this script: 
our %Options;
# Structure for various things: %Scales = (NOTENUMBER => KEY):
our %Scales;
# Is a reference to a 'Midi2Abstract'-object (delivers notes and song-info):
our $Song;
# Version of this programm (X.Y):
our $Version = '0.2';
# Usage string if invoking error of script or if it is requeseted:
our $Usage = <<EOF;
  USAGE: midi2chord [OPTION]
    midichord awaits a standard midi file format (SMF0 or SMF1) in stdin, in
    order to determine and print out chord(s) to stdout. The normal output
    has the following format:

    BAR.BEAT.REST_TICK [MEASURE], (ABSOLUT_TICK): CHORD [| CHORD_ALTERNATIVE(S)]

    There must be at least two or three different notes (depending on
    algorithm) at the same time to be interpreted as a chord (printed with big
    letter at beginning), otherwise it is a single note (noted as small
    letters).

    Single notes will not be printed unless requested with the verbose option.
    They may not be the same amount as you see in your sequencer as notation,
    because tied notes are noted here as one!

  BUGS: Surely too many, yet - sorry. Please note that there is often
    recognized more than one chord at a particulary time.
    It is up to you to figure out the
    chord which fits (sounds) best. This programm tries to find much
    chords. But it is possible that not all possible chords (such as enharmonic
    confusion) will be detected.

    If you think there has been detected a wrong chord, first check if the
    notes are really all at the same position you think, or if there is a note
    which is a small step beneath the others of the "chord" (see the
    "s"-option)! Feel free to improve this script in the terms of the GPL (see
    "http://www.gnu.org"). You are welcome to contact me, go to the
    project-home at "http://sourceforge.net/projects/midi2chord/".

  OPTION:
    -a<NR[,NR[,...]]>
      Select a specific chord-detection algorithm or a set of them. Without the
      option we try all algorithms (could take some time).
      You can use following numbers (algorithms) yet:
        0  pattern based (Fast but less exact. Needs at least 3 complete
           different notes, not only in different octaves!).
        1  intervall-based (Slower but more detailed detection. Needs at least
           2 complete different notes, not only in different octaves!).

    -d
      Print informations maybe helpful for debuging the script (internal
      states,...).

    -e<NR[,NR[,...]]>
      exclude track number (NR) from analysis. This could be usefull
      to prevent tracks without harmonic data (e.g. drums, which is often found
      on track 10 at SMF files) from analysis.

    -h
      Print this help/usage text.

    -s<STEP[,LOWER_BOUND,UPPER_BOUND]>
      The STEP, given as midi-ticks, defines at which position notes will be
      taken for chord-analysis if some exist (e.g. if we have 4/4-measure
      (480 ticks) and a step of 120, notes would be taken every 1/4 (begining
      of beat)).
      If no STEP is given, the standard value is 1.

      The LOWER_BOUND and UPPER_BOUND is also given as ticks. They are counted
      from the STEP-position. All notes between these bounds will belong to one
      chord (this could be useful if it is a "handmade" midifile where some chords
      are recorded from a human, who has not played the single notes of a chord
      all at exactly the same time.). If no BOUNDS are given, it defaults to zero.

    -v
      be verbose by what we do. Thus print the notes of chords and some other
      informations if available.

    -V
      Print version of this programm.
EOF


###############################################################################
# Simple pattern orientated chord detection algorithm.
# IN : Note-list (coded as decimal value (0-127)).
# USE: Reads global "%ChordPatterns".
# OUT: String of recognized chord (there is only one chord possible, since the
#      given notes can match at most one pattern). If we can't determine chord
#      return "undef".
sub Alg0 {
    my @Notes = @_;
    my %NotePattern = map( { $_ => 1 }  map($_ % 12, @Notes));
    my $Pattern = join '_', sort({$a <=> $b} keys %NotePattern);

    # if there are not at least 3 different notes in generated pattern, we
    # don't try to determine chord.
    return undef unless $Pattern =~ /\d+_\d+_\d+.*/;

    # Try to find chord by comparing given pattern with hard-coded pattern:
    foreach my $Chord (keys %ChordPatterns) {
    if ($Pattern =~ /^$Chord/) {
        return $ChordPatterns{$Chord};  # pattern matches (found a chord)
    }
    }

    return undef;  # we haven't found anything yet, thus return undef!
}


###############################################################################
# Mathematical chord detection algorithm (note intervall analysis).
# IN : Note-list (coded as decimal value (0-127)).
# OUT: List of recognized chords if there were given >= 2 different notes
#      as input. If we can't determine chord return "undef".
sub Alg1 {
    ## %Notes = (NOTE => 0), whereby NOTE is a number from 0-11 (c-bb).
    my @Notes = @_;
    my %Notes = map {($_ % 12) => 0} sort {$a <=> $b} @Notes;
    my (@Chord, @Root);

    # we need at least 2 different notes input, or we return undefined:
    return undef if (keys %Notes) <= 1;

    foreach my $Data (GetPossibleRoot(keys %Notes)) {
    push @Root, $Data if defined $Data }  # try to found some root notes

    my @SortedNotes = sort {$a <=> $b} @Notes;  # sort notes to get bass note
    my $Bass = $SortedNotes[0] % 12;  # assuming bass-note is lowest given note

    print "  <Bass=$Bass>\n" if $Options{d};

    ## We have one or more root of a chord (at least Bass and QUINTE (5th)):
    if ((scalar @Root) > 0) {
    ## Determine chord based on the existence of fifth (QUINTE):
    foreach my $Root (@Root) {
        print "  <Root=$Root>\n" if $Options{d};

        # bass-note to <P>rint:
        my $BassP = $Bass != $Root ? "/$Scales{$Bass}" : '';
        # root-note to <P>rint:
        my $RootP = $Scales{$Root};
        # notes-<A>rray (a special one only used in this foreach-loop):
        my @NotesA = keys %Notes;

        #### GENERAL NOTE: The interval "0" and "7" must be in all this
        #### calls of "HasStructure" since we assume to have a root and
        #### Quinte here. Otherwise (for other chords) we call
        #### "GetIncompleteChord".

        #### MAJOR CHORDS:
        if (HasStructure($Root, \@NotesA, 0, 4, 7)) {
        push @Chord, "$RootP$BassP" }
        elsif (HasStructure($Root, \@NotesA, 0, 4, 7, 9)) {
        push @Chord, "$RootP$BassP\[6]" }
        elsif (HasStructure($Root, \@NotesA, 0, 2, 4, 7, 9)) {
        push @Chord, "$RootP$BassP\[6/9]" }
        elsif (HasStructure($Root, \@NotesA, 0, 2, 4, 7)) {
        push @Chord, "$RootP$BassP\[add9]" }
        elsif (HasStructure($Root, \@NotesA, 0, 4, 7, 11)) {
        push @Chord, "$RootP$BassP\[maj7]" }
        elsif (HasStructure($Root, \@NotesA, 0, 4, 7, 10)) {
        push @Chord, "$RootP$BassP\[7]" }
        elsif (HasStructure($Root, \@NotesA, 0, 2, 4, 7, 10)) {
        push @Chord, "$RootP$BassP\[7/9]" }
        elsif (HasStructure($Root, \@NotesA, 0, 4, 7, 9, 10)) {
        push @Chord, "$RootP$BassP\[7/13]" }
        elsif (HasStructure($Root, \@NotesA, 0, 1, 4, 7, 10)) {
        push @Chord, "$RootP$BassP\[7/b9]" }
        elsif (HasStructure($Root, \@NotesA, 0, 2, 4, 7, 9, 10)) {
        push @Chord, "$RootP$BassP\[7/9/13]" }
        elsif (HasStructure($Root, \@NotesA, 0, 1, 4, 7, 9, 10)) {
        push @Chord, "$RootP$BassP\[7/b9/13]" }

        #### MINOR CHORDS:
        elsif (HasStructure($Root, \@NotesA, 0, 3, 7)) {
        push @Chord, "${RootP}m$BassP" }
        elsif (HasStructure($Root, \@NotesA, 0, 3, 7, 9)) {
        push @Chord, "${RootP}m$BassP\[6]" }
        elsif (HasStructure($Root, \@NotesA, 0, 2, 3, 7, 9)) {
        push @Chord, "${RootP}m$BassP\[6/9]" }
        elsif (HasStructure($Root, \@NotesA, 0, 3, 7, 10)) {
        push @Chord, "${RootP}m$BassP\[7]" }
        elsif (HasStructure($Root, \@NotesA, 0, 2, 3, 7, 10)) {
        push @Chord, "${RootP}m${BassP}\[7/9]" }
        elsif (HasStructure($Root, \@NotesA, 0, 3, 7, 11)) {
        push @Chord, "${RootP}m${BassP}\[maj7]" }
        elsif (HasStructure($Root, \@NotesA, 0, 2, 3, 7, 11)) {
        push @Chord, "${RootP}m${BassP}\[maj7/9]" }
        elsif (HasStructure($Root, \@NotesA, 0, 3, 5, 7)) {
        push @Chord, "${RootP}m${BassP}\[add11]" }
        elsif (HasStructure($Root, \@NotesA, 0, 3, 5, 7, 10)) {
        push @Chord, "${RootP}m${BassP}\[7/11]" }

        #### SUS2 CHORDS:
        elsif (HasStructure($Root, \@NotesA, 0, 2, 7)) {
        push @Chord, "${RootP}sus2" }

        #### SUS4 CHORDS:
        elsif (HasStructure($Root, \@NotesA, 0, 5, 7)) {
        push @Chord, "${RootP}sus4" }
        elsif (HasStructure($Root, \@NotesA, 0, 5, 7, 10)) {
        push @Chord, "$RootP\[7/sus4]" }
        elsif (HasStructure($Root, \@NotesA, 0, 5, 7, 9, 10)) {
        push @Chord, "$RootP\[7/13/sus4]" }
        }
    }

    ## We have a chord where we haven't found the root and no QUINTE (5th) seems
    ## to exist:
    else { push @Chord, GetIncompleteChord(@Notes) }

    return @Chord;
}


###############################################################################
# Context based chord detection algorithm. Uses scale info to determine chords.
# IN1: Note-list (coded as decimal value (0-127)).
# IN2: Scale at the actual position.
# OUT: List of recognized chords.
sub Alg3 {
    ## TODO!
    return undef;
}


###############################################################################
# Chord detection algorithm if not intervall of 7 or -5 semitones
# (Root to Quint) is given (chord may be incomplete or it is an
# augmented/diminished chord, which also has no quinte).
# IN : Note-list (coded as decimal value (0-127)).
# OUT: List of recognized chords if there were given >= 2 different notes
#      as input. If we can't determine chord return "undef".
sub GetIncompleteChord() {
    my @Notes = @_;
    my %Notes = map {($_ % 12) => 0} sort {$a <=> $b} @Notes;
    my @Chord;
    my $count = keys %Notes;  # amount of notes
    return undef if $count <= 1;  # we need at least 2 different notes input

    ## Determine bass note of chord (assuming bass-note is lowest given note):
    my @SortedNotes = sort {$a <=> $b} @Notes;
    my $Bass = $SortedNotes[0] % 12;

    # Interval of six semitones noted with scalar 'dim5'.
    # Put intervals in %Notes, by subtracting each one from the one above
    my $Prev = 100;  # dummy neg. value in 1st element (not used).
    my $dim5 = 0;
    # %Notes: $Notes{NOTE_0-11} = (DISTANCE_TO_PREVIOUS_NOTE).
    foreach my $Note1 (sort {$a <=> $b} keys %Notes) {
    $Notes{$Note1} = $Note1 - $Prev;  # intervall to previous note
    $Prev = $Note1;
    foreach my $Note2 (sort {$a <=> $b} keys %Notes) {
        ++$dim5 if ($Note2 - $Note1 == 6);
    }
    }

    ## we have 2 notes (less should not occur - see above!):
    if ($count == 2) {
    foreach my $Note (sort {$a <=> $b} keys %Notes) {
        push @Chord, "$Scales{$Note}(no5th)"
        if $Notes{$Note} == 8 && $Note == $Bass;  # 12-8=4 (terz)
        push @Chord, "$Scales{$Note-4}(no5th)"
        if $Notes{$Note} == 4 && $Note != $Bass;  # 4 (terz)
        push @Chord, "$Scales{$Note}m(no5th)"
        if $Notes{$Note} == 9 && $Note == $Bass;  # 12-9=3 (minor terz)
        push @Chord, "$Scales{$Note-3}m(no5th)"
        if $Notes{$Note} == 3 && $Note != $Bass;  #3 (minor terz)
        push @Chord, "$Scales{$Note}/$Scales{($Note-8)%12}(no5th)"
        if $Notes{$Note} == 8 && $Note != $Bass;  # 1. inversion
        push @Chord, "$Scales{$Note-4}/$Scales{$Note}(no5th)"
        if $Notes{$Note} == 4 && $Note == $Bass;  # 1. inversion
        push @Chord, "$Scales{$Note}m/$Scales{($Note-9)%12}(no5th)"
        if $Notes{$Note} == 9 && $Note != $Bass;  # 1. inversion
        push @Chord, "$Scales{$Note-3}m/$Scales{$Note}(no5th)"
        if $Notes{$Note} == 3 && $Note == $Bass;  # 1. inversion
    }
    }

    ## we have at least 3 notes:
    elsif ($count >= 3) {
    # dim5 counted above between any note in chord.
    my ($min3, $maj3, $min6, $maj6, $aug4) = (0, 0, 0, 0, 0);
    foreach my $Note (keys %Notes) {
        $min3++ if $Notes{$Note} == 3;
        $maj3++ if $Notes{$Note} == 4;
        $aug4++ if $Notes{$Note} == 6;
        $min6++ if $Notes{$Note} == 8;
        $maj6++ if $Notes{$Note} == 9;
    }
    ## Augmented chords:
    if ($maj3 == 2 && $count == 3) {
        push @Chord, "$Scales{$Bass}\[+]";
        push @Chord, "$Scales{($Bass+4)%12}/$Scales{$Bass}\[+]";
        push @Chord, "$Scales{($Bass+8)%12}/$Scales{$Bass}\[+]";
    }
    ## Diminished chords:
    if ($min3 == 3 && $count == 4) {
        push @Chord, "$Scales{$Bass}\[dim7]";
        push @Chord, "$Scales{($Bass+3)%12}/$Scales{$Bass}\[dim7]";
        push @Chord, "$Scales{($Bass+6)%12}/$Scales{$Bass}\[dim7]";
        push @Chord, "$Scales{($Bass+9)%12}/$Scales{$Bass}\[dim7]";
    }

    ## Is it a 7/#11(b5) chord?
    ##      6               4                8
    if (($dim5 >= 1) && (($maj3 >= 1) || ($min6 >= 1))) {
        # determine root from terz:    
        my $Root = 0;
        foreach my $Note (keys %Notes) {
        if ($Notes{$Note} == 4) { $Root = $Note - 4 }  # note is terz
        if ($Notes{$Note} == 8) { $Root = $Note }  # note is root
        }

        # bass-note to <P>rint:
        my $BassP = $Bass != $Root ? "/$Scales{$Bass}" : '';
        my @NotesA = keys %Notes;  # Note-array

        if (HasStructure($Root, \@NotesA, 0, 4, 6, 10)) {
        push @Chord, "$Scales{$Root}$BassP\[7/#11(b5)]" }
    }
    }

    scalar @Chord == 0 ? return undef : return @Chord;
}


###############################################################################
# Determines possible root notes by intervall checking
# IN : Note-list (coded as decimal value (0-11 = c-bb)).
# OUT: List of recognized root notes. If we found nothing return "undef".
sub GetPossibleRoot() {
    my @Notes = @_;
    my @Roots;  # holds all recognized root notes

    # Determine POSSIBLE root(s) of a chord by searching a intervall of a
    # QUINTE (5semitones). Note that the bass-note may not necesarily be the
    # same note as root (inversion)!
    # Store this notes (with a difference of 7 semitones (fifth intervall
    # (12-5)) or -5 semitones (fourth intervall (0-5))) in @Root.
    foreach my $Note1 (sort {$a <=> $b} @Notes) {
    foreach my $Note2 (sort {$a <=> $b} @Notes) {
        if (($Note2 - $Note1 == 7) || ($Note2 - $Note1 == -5)) {
        print "  <rootpass=$Note2-$Note1>" if $Options{d};
        push @Roots, $Note1;
        }}}

    return @Roots;
}


###############################################################################
# True if given intervall-list matches to given notes-ref. (amount and position
# to root note).
# IN1: Root of chord ((coded as decimal value (0-11 = c-bb)).
# IN2: Reference to array (note-list, coded as decimal value (0-11 = c-bb)).
# IN3: Intervall list (0-11) which is count from the root note.
# OUT: Number of notes which maches the intervalls. This is always the same as
#      the number of given intervalls - if the given structure matches with
#      the given note list. If it doesn't match we return "undef".
sub HasStructure() {
    my $Root = shift;  # single root note
    my $Notes = shift;  # array reference
    my @Intervalls = @_;  # the rest are intervalls
    # Some <C>ounters:
    my ($CIntervalls, $CMatches, $CNotes) =
    (scalar @Intervalls, 0, scalar @{$Notes});

    # Given data is not valid since the number of notes is not the same as the
    # number of intervalls. If we wouldn' t check this, a "C7" could go as a
    # e.g. "C7/9", since the last contains the same intervalls as the
    # first. But it contains one more (9th) - and that is our problem: we want
    # a exact match so that the "9" will be only detected if we had a "9"!!
    return undef if $CNotes != $CIntervalls;

    foreach my $Intervall (@Intervalls) {
    foreach my $Note (@{$Notes}) {
        ++$CMatches if $Note == (($Root + $Intervall) % 12) }}

    $CMatches == $CNotes ? return $CMatches : return undef;
}


###############################################################################
# Manages output of chords. Thus if we should print each cord or only such
# chords which are at a special intervall (e.g. at first beat of a bar).
sub PrintChord {
    print "SMF-Format: " . $Song->Info('format') . "\n" .
      "Division(PPQN): " . $Song->Info('division') . "\n"
      if $Options{v};

    # while we are not at the end of song we step to next position of notes:
    while ( $Song->IsInSong() )  {
    # Get Position and Tick before Notes(), because it increments position.:
    my $Position = $Song->Position();
    my $Tick = $Song->Tick();
    my $Numerator = $Song->Info('numerator');
    my $Denumerator = $Song->Info('denumerator');
    my @Notes = $Song->Notes(take => 'auto');
    next if (not defined $Notes[0]);  # && (scalar @Notes == 1));

    my ($Chord, @Chords, %UniqueList);

    # Print all notes at each step-position if in verbose-mode (-v):
    if ($Options{v}) {
        my $Notes;
        if (scalar @Notes >= 2) {
        $Notes = join(', ', map(lc $MIDI::number2note{$_}, @Notes));
        }
        elsif ((scalar @Notes == 1) && (defined $Notes[0])) {
        $Notes = lc $MIDI::number2note{$Notes[0]};
        }
        else { $Notes = 'no note' }
        
        $Notes =~ s/s/#/g;  # make notes like 'cs' to 'c#'
        printf("  %s [%s/%s], (%6s): %s\n", $Position, $Numerator,
           $Denumerator, $Tick, $Notes);
    }

    ## Call a ALGORITHM if it is explicit choosed with a-option
    ## or if no option is given:
    push @Chords, Alg0(@Notes)
        if ((exists $Alg[0]) || (! exists $Options{a}));
    push @Chords, Alg1(@Notes)
        if ((exists $Alg[1]) || (! exists $Options{a}));

    ## Make a list of all chords; extract multiples of one chord.
    foreach my $Key (@Chords) { $UniqueList{$Key} = 1 if defined $Key }

        ## Skip to next step if we couldn' t determine a chord:
    if (keys %UniqueList == 0) { next }
    elsif (keys %UniqueList == 1) { $Chord = (%UniqueList)[0] }
    else { $Chord = join(' | ', keys %UniqueList) }

    # Print chords:
    printf("%s [%s/%s], (%6s): %s\n", $Position, $Numerator, $Denumerator,
           $Tick, $Chord);
    }
}


###############################################################################
###############################################################################
## START OF SCRIPT-EXECUTION.  :-)                                           ##
###############################################################################
###############################################################################

# %Options = (OPTION => VALUE). Note that VALUE contains all data as string!!
# E.g. option "5,6" will be "-e5,6" in $Options{e}.
getopts('a:e:s:hdvV', \%Options);

# USAGE:
if    ($Options{h}) { print $Usage; }

# VERSION:
elsif ($Options{V}) { print "$Version\n"; }

# CHORD-DETECTION:
else {
    # OPTION "a"; create global list (@Alg) with algs to process:
    if (exists $Options{a}) {
    foreach my $AlgNr (split(/,/, $Options{a})) {
        $Alg[$AlgNr] = 'do';
        print "Do Alg$AlgNr! " if $Options{d};
    }
    }

    my %S_Option;
    if (defined $Options{s}) {  # need to overwrite std. values?
    # tests for numeric argument and the correct amount of arguments
    if ($Options{s} =~ /^(\d+)$/) {  # 1 argument
        %S_Option = (step => $1, lbound => 0, ubound => 0); }
    elsif ($Options{s} =~ /^(\d+),(\d+),(\d+)$/) { # 3 arguments
        %S_Option = (step => $1, lbound => $2, ubound => $3); }
    else {
        die "Option \"s\" has no correct (numeric) arguments!" }
    }
    else { %S_Option = (step => 1, lbound => 0, ubound => 0); }

    $Song = Midi2Abstract->New();
    $Song->Filter(channels => $Options{e}, step => $S_Option{step}, lbound =>
          $S_Option{lbound}, ubound => $S_Option{ubound},
          note_count => 1);

    # (NUMERIC_PATTERN => CHORD); Chord 'C' consists of the notes
    # 'c-e-g' which is in numeric '0,4,7'.
    %ChordPatterns = (
    ## MAJOR (german: Dur):
    '0_4_7' => 'C', '1_5_8' => 'C#', '2_6_9' => 'D', '3_7_10' => 'D#',
    '4_8_11' => 'E', '0_5_9' => 'F', '1_6_10' => 'F#', '2_7_11' => 'G',
    '0_3_8' => 'G#', '1_4_9' => 'A', '2_5_10' => 'A#', '3_6_11' => 'B',
    ## MINOR (german: Moll):
    '0_3_7' => 'Cm', '1_4_8' => 'C#m', '2_5_9' => 'Dm', '3_6_10' => 'D#m',
    '4_7_11' => 'Em', '0_5_8' => 'Fm', '1_6_9' => 'F#m', '2_7_10' => 'Gm',
    '3_8_11' => 'G#m', '0_4_9' => 'Am', '1_5_10' => 'A#m', '2_6_11' => 'Bm');

    %Scales = ( 0 => 'C', 1 => 'C#', 2 => 'D', 3 => 'D#', 4 => 'E', 5 => 'F',
        6=> 'F#', 7 => 'G', 8 => 'G#', 9 => 'A', 10 => 'A#', 11 => 'B');

    # The main routine! It does at least what we wanted (printing out chords)...
    PrintChord();
}

exit 0;

perl midi2chord -h 或者 perl midi2chord --help 查看帮助。

【热门文章】
【热门文章】