Skip to content

Raku-Noise-Gang/perl6-Audio-MIDI-Note

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

22 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

NAME

Audio::MIDI::Note - playable MIDI note

DESCRIPTION

This module lets you play notes on a MIDI hardware or software device using methods that allow to replicate sheet music.

TABLE OF CONTENTS

NON Perl 6 REQUIREMENTS

This module uses Audio::PortMIDI under the hood, which requires portmidi C library. On Debian and derivatives, you can install it with sudo apt-get install libportmidi0

SYNOPSIS

Canon by Johann Pachelbel (in C key)

Replication of this sheet music. Note: on my system, the midi player on that page kills timidity and I have to do sudo service timidity restart to get my scripts to work again...

    use Audio::PortMIDI;
    use Audio::MIDI::Note;

    my $stream = Audio::PortMIDI.new.open-output: 3, 32;
    END { $stream.close }

    my Audio::MIDI::Note $note .= new: :20tempo :$stream, :value(½), :49velocity;

    # Pachelbel `Canon` (in C key)
    # Comments reference this sheet music: http://www.8notes.com/scores/420.asp
    $note   .play(<C4 E4>).play(<G3 D4>)  # first line of bars, with one repeat
            .play(<A3 C4>).play(<E3 B3>)
            .play(<F3 A3>).play(<C3 G3>)
            .play(<F3 A3>).play(<G3 B3>)
    for ^2;

    $note   .play(<C4 G4 E5>).play(<G3 B4 D5>) # second line of bars
            .play(<A3 C5   >).play(<E3 G4 B4>)
            .play(<F3 C4 A4>).play(<C3 E4 G4>)
            .play(<F3 F4 A4>).play(<G3 D4 B4>)

            # first two notes of the chord are half-notes and third one is a crotchet,
            # so we play the half-notes in async with .aplay, and then do
            # the crotchet series with blocking .play
            .velocity(64).value(¼) # play louder and switch to quarter note default
            .aplay(<C4 E4>, ½).play('C5').play('C5')
            .aplay(<G3 D4>, ½).play('D5').play('B4')

            # 10th bar
            .aplay(<A3 C4>, ½).play('C5').play('E5')
            .aplay('E3',    ½).play('G5').play('G4')

            # 11th and 12th bars
            .aplay(<F3 A3>, ½).play('A4').play('F4')
            .aplay('C3'   , ½).play('E4').play('G4')
            .aplay(<F3 A3>, ½).play('F4').play('C5')
            .aplay(<G3 B3>, ½).play('B5').play('G4')

            # 13th bar; after the first chord, we're asked to play louder (velocity)
            .aplay(<C4 E4>, ½).play('C5')
            .velocity(80)
            .play('E5', ⅛).play('G5', ⅛).play('G5', ⅛)
            .play('A5', ⅛).play('G5', ⅛).play('F5', ⅛)

            .aplay(<A3 C4>, ½).play('E5', ¼+⅛).play('E5', ⅛)
            .aplay(<E3 G3>, ½).play('E5',   ⅛).play('F5', ⅛).play('E5', ⅛).play('D5', ⅛)

            # 15th, 16th bar
            .aplay(<F3 A3>, ½).play('C5', ⅛).play('Bb4', ⅛).play('A4', ⅛).play('Bb4', ⅛)
            .aplay(<C3 E3>, ½).play('G4').play('E4')
            .aplay(<F3 A3>, ½).play('C4').play('F4', ⅛).play('E4', ⅛)
            .aplay(<G3 B3>, ½).play('D4').play('G4', ⅛).play('F4', ⅛)

            # 17th bar: we'll sound half-notes in async, and will use .rest
            # to play the quarter-note rest on the treble clef.
            # .rest can take a rest value as argument, but our current value is
            # already a crotchet, so no argument is needed:
            .aplay(<C4 E3>, ½).rest.velocity(64).play('C5')
            .aplay('G3', ½).play('D5').play('B4')

            # Last row of bars
            .aplay(<A3 C4>, ½).play('C5').play('E4')
            .aplay(<E3 B3>, ½).play('G4', ¼+⅛).play('A4', ⅛)
            .aplay(<F3 A3>, ½).play('F4').play('C4')
            .aplay(<C3 G3>, ½).play('E4').play('G4')
            .aplay(<F3 A3>, ½).play('F4').play('E4')
            .aplay(<G3 B3>, ½).play('D4').play('G4')
            .play(<C3 C4 E3>, 1)
    ;

Gorgoroth - A World to Win Solo

Part of the solo from A World to Win, showing swap of instruments, re-use of repeating pieces of music, calculation of triplet note values, and use of on- and off-beat velocity shortcuts.

    use Audio::MIDI::Note;

    my $stream = Audio::PortMIDI.new.open-output: 3, 32;
    END { $stream.close }

    my Audio::MIDI::Note $note .= new: :31tempo :30instrument :$stream :value(⅔ * ⅛);

    # Looping `Gorgoroth - A World to Win` solo with organ chord in the background.
    # We use triplet notes and save repeating pieces into variables for reuse
    my &rhythm = *.play('D#5').play('D5').play('C5');
    my &riff = {
        .aplay(<C4 E4 G4>, 4, :19instrument, :40velocity)

        .play('C5',  ⅔*+⅛) )
        .riff(&rhythm).play('C5',  ⅔*+⅛) )
        .riff(&rhythm).play('G#4', ⅔*+⅛) )
        .riff(&rhythm).play('A#4', ⅔*+⅛) )
        .riff(&rhythm).play('C5',  ⅔*+⅛) )
        .riff(&rhythm).play('C5',  ⅔*+⅛) )
        .riff(&rhythm);
    };

    $note   .riff(&riff)
                .play('G5',  ¼, :on).play('F5',  ¼, :off)
                .play('D#5', ¼     ).play('D5',  ¼, :off)
            .riff(&riff)
                .play('F5',  ¼,:on ).play('D#5', ¼, :off)
                .play('D5',  ¼     ).play('A#4', ¼, :off)
    for ^10;

PLAYING TIPS

  • Save repeating chunks into subs and play them with .riff method
  • Play the piece using the shortest notes and rests it requires, using .play/.rest methods. Use the asynchronous .aplay method to play longer notes that overlap these shorter ones.

ATTRIBUTES

Note: to facilitate chaining, all attributes can be either assigned to directly or be given the new value as an argument:

    $note.value = ½;
    $note.value(½); # same as above; returns invocant

:stream

The Audio::PortMIDI::Stream object opened at a MIDI output device. See Audio::PortMIDI for details.

:tempo

Positive Int. Specifies the tempo of the piece in beats per minute per WHOLE note. Defaults to: 40

:value

Numeric. Specifies the default value (amount of time it rings) of the played notes. Defaults to: ¼

:velocity

0 <= Int <= 127. Specifies the default velocity (similar to volume) of the played notes; 127 indicates as loud as possible. Defaults to: 80 (mf loudness in sheet music).

:instrument

Int. MIDI Patch code for the default instrument to use. Defaults to: 0 (piano)

:channel

Int. Specifies the MIDI channel to use. Defaults to: 0

METHODS

.new

my Audio::MIDI::Note $note .= new:
    :stream(Audio::PortMIDI.new.open-output: 3, 32)
    :20tempo
    :value(½)
    :49velocity
    :30instrument
    :0channel;

Creates and returns a new Audio::MIDI::Note object. See ATTRIBUTES section for details on the accepted parameters.

.aplay

    $note.aplay('C5')
         .aplay(<C4 E4 G4>, ⅛, :on-on, :100velocity, :30instrument);

Same as .play, but is asynchronous and returns immediately. Useful to play longer notes that overlap shorter ones (that you would play with .play). Takes the same arguments as .play

.play

    $note.play('C5')
         .play(<C4 E4 G4>, ⅛, :on-on, :100velocity, :30instrument);

Plays one or more notes (simultaneously). Takes the following arguments:

First positional

An Stror a List of strings representing the notes to play. Multiple notes will be played at the same time, not consecutively. Valid notes are from C0 through G#10/Ab10. Use hashmark (#) to indicate sharps and lower-case B (b) to indicate flats. This argument is case-sensitive.

Second positional

Note value for the currently played notes. Defaults to: the value of :value attribute.

:velocity

Note velocity for the currently played notes. Defaults to: the value of :velocity attribute.

:instrument

MIDI patch code for the instrument for the currently played notes. Defaults to: the value of :instrument attribute.

:on-on, :on, :off

Velocity control shortcuts for on/off beats. :on-on is the loudest, :on is less so, but still louder than normal velocity; :off is less loud than normal velocity.

.rest

    $note.rest
         .rest(⅛);

"Plays" a rest by blocking execution for the specified note value. Takes Numeric note value, which defaults to: the value of :value attribute.

.riff

    my &riff = *.play('C4').play('E4').play('G4');

    $note.&riff;
    $note.riff(&riff);

The last two lines in the code above are equivalent. The only issue is you can't split a method chain that uses &riff onto multiple lines. This is where the .riff method comes into play. It takes your Callable as an argument and lets you break up your method chains on multiple lines.

Storing bits of music into variables and then playing them with .riff lets you re-use repeating bars or fragments in a piece of music.

Also worth noting: method chains started on a WhateverStar also cannot be broken up into multiple lines. Just use a set of curlies instead:

    my &riff = {
        .play('C4').play('E4')
        .play('G4').play('C5');
    }

REPOSITORY

Fork this module on GitHub: https://github.com/zoffixznet/perl6-Audio-MIDI-Note

BUGS

To report bugs or request features, please use https://github.com/zoffixznet/perl6-Audio-MIDI-Note/issues

AUTHOR

Zoffix Znet (http://zoffix.com/)

LICENSE

You can use and distribute this module under the terms of the The Artistic License 2.0. See the LICENSE file included in this distribution for complete details.

About

Perl 6 module: play notes via MIDI

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Other 100.0%