File Coverage

blib/lib/MIDI/Morph.pm
Criterion Covered Total %
statement 42 42 100.0
branch 15 18 83.3
condition 9 18 50.0
subroutine 7 7 100.0
pod 5 5 100.0
total 78 90 86.6


line stmt bran cond sub pod time code
1             package MIDI::Morph;
2              
3 6     6   166640 use warnings;
  6         15  
  6         366  
4 6     6   31 use strict;
  6         11  
  6         4643  
5              
6             our @ISA = qw(Exporter);
7             our @EXPORT_OK = qw(event_distance);
8              
9             =head1 NAME
10              
11             MIDI::Morph - Musical transition tool
12              
13             =head1 VERSION
14              
15             Version 0.01
16              
17             =cut
18              
19             our $VERSION = '0.02';
20              
21             =head1 SYNOPSIS
22              
23             use MIDI::Morph;
24              
25             my $m = MIDI::Morph->new(from => $from_score, to => $to_score);
26             $new_score = $m->Morph(0.4);
27              
28             =head1 DESCRIPTION
29              
30             The aim of MIDI::Morph is to provide an easy-to-use composition tool that allows
31             transitions between two I (musical snippets). The data handled by
32             this module is in L format (at this moment, only C events
33             are considered).
34              
35             This is an alpha release, features and API will be extended and changed
36             iteratively.
37              
38             =head1 CONSTRUCTOR
39              
40             =head2 new
41              
42             my $m = MIDI::Morph->new(from => $from, to => $to);
43              
44             Creates a new morpher object.
45              
46             =cut
47              
48             sub new {
49 5     5 1 1029 my $class = shift;
50 5         18 my %params = @_;
51 5         11 my $self = {};
52              
53             return undef
54 5 100 66     54 unless ref $params{from} eq 'ARRAY' && ref $params{to} eq 'ARRAY';
55              
56 4         16 foreach (qw(from to)) {
57 8         24 $self->{$_} = $params{$_};
58             }
59              
60 4         19 return bless $self, $class;
61             }
62              
63             =head1 METHODS
64              
65             =head2 AutoMap
66              
67             AutoMap is called automatically by MIDI::Morph and provides a mapping from
68             the notes in the C structure to the notes in the C structure.
69             Currently, it is a simple mapping 1st<->1st, 2nd<->2nd, but this will
70             become more sophisticated in future.
71              
72             =cut
73              
74             sub AutoMap {
75 4     4 1 8 my ($self) = @_;
76              
77 4         11 $self->{map} = [];
78              
79 4         10 foreach (0 .. $#{$self->{from}}) {
  4         17  
80 13         54 $self->{map}->[$_] = [$_];
81             }
82             }
83              
84             =head2 Morph
85              
86             $m->Morph($position);
87              
88             Morph creates a structure that reflects a transition point between C (0)
89             and C (1). Currently the transition is linear.
90              
91             =cut
92              
93             sub Morph {
94 12     12 1 5014 my ($self, $position) = @_;
95              
96 12 100       78 $self->AutoMap()
97             unless (ref $self->{map});
98              
99 12         42 my @morph = ();
100 12         18 foreach (0 .. $#{$self->{map}}) {
  12         39  
101 39         107 push @morph,
102             morph_single_event($self->{from}->[$_], $self->{to}->[$_], $position);
103             }
104              
105 12         83 return [@morph];
106             }
107              
108             =head1 FUNCTIONS
109              
110             =head2 event_distance
111              
112             MIDI::Morph::event_distance($event1, $event2, $weights);
113              
114             This function calculates the distance between two events. The events passed
115             should be note events as described in L. The weights are passed
116             as a hash reference with the keys C, C, C and C.
117             This parameter is optional; the default weights are 1, 1, 1 and 0 respectively.
118              
119             These weights can be used in case you want to measure the distance between
120             two events in different terms.
121              
122             =cut
123              
124             our %distance_default_weights = (
125             start => 1,
126             end => 1,
127             pitch => 1,
128             velocity => 0);
129              
130             our %distance_weights = %distance_default_weights;
131              
132             sub event_distance {
133 35     35 1 1798 my ($a, $b, $weights) = @_;
134              
135             # 'note', position, duration, channel, pitch, velocity
136             # 0 1 2 3 4 5
137 35 100 66     296 return undef unless ref $a eq 'ARRAY' && ref $b eq 'ARRAY';
138 34 100 66     192 return undef unless scalar @$a == 6 && scalar @$a == 6;
139 33 50 33     182 return undef unless $a->[0] eq 'note' && $b->[0] eq 'note';
140              
141 33         140 my %weights = %distance_weights;
142              
143 33 100       415 if (ref $weights eq 'HASH') {
144 32         80 foreach (keys %weights) {
145 128 100       579 $weights{$_} = $weights->{$_} if defined $weights->{$_};
146             }
147             }
148              
149             # use Data::Dumper qw(Dumper);
150             # print STDERR "\n\n" . Dumper({
151             # weights => \%weights,
152             # a => $a,
153             # b => $b
154             # }). "\n\n";
155              
156             return
157 33         465 abs($a->[1] - $b->[1]) * $weights{start} +
158             abs(($a->[1] + $a->[2]) - ($b->[1] + $b->[2])) * $weights{end} +
159             abs($a->[4] - $b->[4]) * $weights{pitch} +
160             abs($a->[5] - $b->[5]) * $weights{velocity};
161             }
162              
163             =head2 morph_single_event
164              
165             my $event = morph_single_event($from_event, $to_event, $position);
166              
167             This helper function morphs two single events.
168              
169             =cut
170              
171             sub morph_single_event {
172 39     39 1 52 my ($from, $to, $position) = @_;
173              
174 39 50 33     196 return undef unless ref $from eq 'ARRAY' && ref $to eq 'ARRAY';
175 39 50 33     186 return undef unless $from->[0] eq 'note' && $to->[0] eq 'note';
176              
177 39         115 my @event = @$from;
178              
179             # leave channel untouched, change start, duration, pitch, velocity
180 39         60 foreach (1, 2, 4, 5) {
181 156         190 my $diff = $to->[$_] - $from->[$_];
182 156         289 $event[$_] = $from->[$_] + $position * $diff;
183             }
184              
185 39         155 return [@event];
186             }
187              
188             =head1 AUTHOR
189              
190             Christian Renz, Ecrenz @ web42.comE
191              
192             =head1 BUGS
193              
194             Please report any bugs or feature requests to
195             C, or through the web interface at
196             L.
197             I will be notified, and then you'll automatically be notified of progress on
198             your bug as I make changes.
199              
200             =head1 SEE ALSO
201              
202             L
203              
204             =head1 COPYRIGHT & LICENSE
205              
206             Copyright 2005 Christian Renz Ecrenz @ web42.comE , All Rights Reserved.
207              
208             This program is free software; you can redistribute it and/or modify it
209             under the same terms as Perl itself.
210              
211             =cut
212              
213             42;