File Coverage

blib/lib/Audio/LADSPA/Network.pm
Criterion Covered Total %
statement 31 197 15.7
branch 1 78 1.2
condition 2 14 14.2
subroutine 9 30 30.0
pod 15 22 68.1
total 58 341 17.0


line stmt bran cond sub pod time code
1             # Audio::LADSPA perl modules for interfacing with LADSPA plugins
2             # Copyright (C) 2003 - 2004 Joost Diepenmaat.
3             #
4             # This program is free software; you can redistribute it and/or modify
5             # it under the terms of the GNU General Public License as published by
6             # the Free Software Foundation; either version 2 of the License, or
7             # (at your option) any later version.
8             #
9             # This program is distributed in the hope that it will be useful,
10             # but WITHOUT ANY WARRANTY; without even the implied warranty of
11             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12             # GNU General Public License for more details.
13             #
14             # You should have received a copy of the GNU General Public License
15             # along with this program; if not, write to the Free Software
16             # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17             #
18             # See the COPYING file for more information.
19              
20             package Audio::LADSPA::Network;
21 4     4   101554 use strict;
  4         8  
  4         200  
22             our $VERSION = "0.021";
23 4     4   11093 use Audio::LADSPA;
  4         12  
  4         144  
24 4     4   3459 use Graph::Directed;
  4         753285  
  4         116  
25 4     4   38 use Carp;
  4         9  
  4         321  
26 4     4   20 use base qw(Class::Publisher);
  4         8  
  4         5812  
27              
28             sub new {
29 1     1 1 614 my ($class,%args) = @_;
30 1   50     15 my $self = bless {
      50        
31             graph => Graph::Directed->new,
32             sample_rate => $args{sample_rate} || 44100,
33             buffer_size => $args{buffer_size} || 1024,
34             run_order => undef,
35             plugin_by_uniqid => {},
36             %args,
37             },$class;
38 1         288 $self->notify_subscribers("new",$self);
39 1         217 return $self;
40             }
41              
42             sub sample_rate {
43 0     0 0 0 my $self = shift;
44 0         0 return $self->{sample_rate};
45             }
46              
47             sub buffer_size {
48 0     0 0 0 my $self = shift;
49 0         0 return $self->{buffer_size};
50             }
51              
52             sub _make_plugin {
53 0     0   0 my $self = shift;
54 0         0 my $plugin;
55 0 0       0 if (@_ == 1) {
56 0         0 $plugin = shift;
57 0 0       0 unless (ref ($plugin)) {
58 0         0 $plugin = $plugin->new($self->{sample_rate});
59             }
60             }
61             else {
62 0         0 $plugin = Audio::LADSPA->plugin(@_)->new($self->{sample_rate});
63             }
64 0         0 $plugin->set_monitor($self); # register callbacks.
65 0         0 return $plugin;
66             }
67              
68             sub graph {
69 1     1 0 12 return $_[0]->{graph};
70             }
71              
72             sub add_plugin {
73 0     0 1 0 my ($self) = shift;
74 0         0 my $plugin = $self->_make_plugin(@_);
75 0         0 $self->graph->add_vertex("$plugin");
76 0         0 $self->graph->set_vertex_attribute("$plugin",'plugin',$plugin);
77 0         0 for ($plugin->ports()) {
78 0 0       0 $self->_connect_default($plugin,$_) unless $plugin->get_buffer($_);
79             }
80 0         0 $self->{plugin_by_uniqid}->{$plugin->get_uniqid} = $plugin;
81 0         0 $self->notify_subscribers("add_plugin",$plugin);
82 0         0 return $plugin;
83             }
84              
85             sub plugins {
86 1     1 1 3 my ($self) = @_;
87 1 50       6 if (!$self->{run_order}) {
88 1 0       6 $self->{run_order} = [ map { my $p = $self->graph->get_vertex_attribute("$_",'plugin'); $p ? $p : () } $self->graph->toposort() ];
  0         0  
  0         0  
89             }
90              
91 1         1038 return @{$self->{run_order}};
  1         7  
92             }
93              
94             sub has_plugin {
95 0     0 1 0 my ($self,$plugin) = @_;
96 0         0 return $self->graph->has_vertex("$plugin");
97             }
98              
99             sub add_buffer {
100 0     0 1 0 my ($self,$buff) = @_;
101 0 0       0 if (!ref $buff) {
102 0         0 $buff = Audio::LADSPA::Buffer->new($buff);
103             }
104 0         0 $self->graph->add_vertex("$buff");
105 0         0 $self->graph->set_vertex_attribute("$buff",'buffer',$buff);
106 0         0 $self->notify_subscribers("add_buffer",$buff);
107 0         0 return $buff;
108             }
109              
110             sub buffers {
111 0     0 1 0 my ($self) = @_;
112 0 0       0 return map { my $b = $self->graph->get_vertex_attribute("$_",'buffer'); $b ? $b : () } $self->graph->vertices();
  0         0  
  0         0  
113             }
114             sub has_buffer {
115 0     0 1 0 my ($self,$buffer) = @_;
116 0         0 return $self->graph->has_vertex("$buffer");
117             }
118              
119              
120             sub _connect_default {
121 0     0   0 my ($self,$plugin,$port) = @_;
122 0 0       0 croak "Logic error: port $port already connected" if ($plugin->get_buffer($port));
123 0         0 my $buffer;
124 0 0       0 if ($plugin->is_control($port)) {
125 0         0 $buffer = $self->add_buffer(1);
126 0         0 $buffer->set($plugin->default_value($port));
127             }
128             else {
129 0         0 $buffer = $self->add_buffer($self->{buffer_size});
130             }
131 0 0       0 warn "monitor != network for plugin $plugin" if $plugin->monitor ne $self;
132 0         0 $plugin->connect($port,$buffer);
133             }
134              
135             sub DESTROY {
136 1     1   650 my ($self) = @_;
137             # disconnect all plugins, otherwise the buffers might not
138             # be freed (?) I think I already fixed that, but anyway...
139            
140 1         6 for ($self->plugins()) {
141             # $_->disconnect_all();
142 0         0 $self->delete($_);
143             }
144              
145 1         4 $self->{plugin_by_uniqid} = {};
146 1         4 $self->{run_order} = undef;
147            
148             # this is not really needed, but it make for a nice place
149             # for things to break down, if I mix up the reference counts again.
150 1         14 delete $self->{graph};
151 1         13 $self->delete_all_subscribers();
152             }
153              
154             sub run {
155 0     0 1   my ($self,$samples) = @_;
156 0 0         croak "Cannot run for more than buffer_size samples" if ($samples > $self->{buffer_size});
157 0 0         croak "Invalid sample number: $samples" if $samples < 1;
158 0           for ($self->plugins) {
159 0           $_->run($samples);
160             }
161             }
162              
163             sub connect {
164 0     0 1   my ($self,$from_plug,$from_port,$to_plug,$to_port) = @_;
165 0 0         if ($from_plug eq $to_plug) {
166 0           warn "Cannot create loop to self";
167 0           return 0;
168             }
169 0 0 0       unless ($from_plug->is_input($from_port) xor $to_plug->is_input($to_port)) {
170 0           warn "Can only connect input to output";
171 0           return 0;
172             }
173 0 0         if ($from_plug->is_input($from_port)) {
174 0           ($from_plug,$from_port,$to_plug,$to_port) = ($to_plug,$to_port,$from_plug,$from_port);
175             }
176 0 0 0       if ($from_plug->is_control($from_port) and ! $to_plug->is_control($to_port)) {
177 0           warn "Can not connect from control to audio port (you CAN do it the other way around)";
178 0           return 0;
179             }
180 0           for ($from_plug,$to_plug) {
181 0 0         $self->add_plugin($_) unless $self->has_plugin($_);
182             }
183             # note that connecting the other way around will create
184             # problems when connecting an audio-out to crontrol-in port
185 0           my $buffer = $from_plug->get_buffer($from_port);
186 0           my $ret = $to_plug->connect($to_port => $buffer);
187 0 0         $self->notify_subscribers("connect",$from_plug,$from_port,$to_plug,$to_port) if $ret;
188 0           return $ret;
189             }
190              
191             sub disconnect {
192 0     0 1   my ($self,$plug,$port) = @_;
193 0           $plug->disconnect($port);
194 0           $self->_connect_default($plug,$port);
195 0           $self->notify_subscribers("disconnect",$plug,$port);
196             }
197              
198             sub cb_connect {
199 0     0 0   my ($self,$plug,$port,$buffer) = @_;
200 0           for ($plug,$port,$buffer) {
201 0 0         croak("Undef'd plug/port/buffer") unless defined $_;
202             }
203 0 0         if (!$self->has_buffer($buffer)) {
204 0           $self->add_buffer($buffer);
205             }
206 0 0         if (!$self->has_plugin($plug)) {
207 0           $self->add_plugin($plug);
208             }
209              
210             # try out to see if we get cycles
211 0           my $H = $self->graph->copy();
212 0 0         if ($plug->is_input($port)) {
213 0           $H->add_edge("$buffer","$plug");
214             }
215             else {
216 0           $H->add_edge("$plug","$buffer");
217             }
218 0 0         if ($H->has_a_cycle) {
219 0           return 0;
220             }
221              
222             # fine, now for real
223 0 0         if ($plug->is_input($port)) {
224 0           $self->graph->add_edge("$buffer","$plug");
225 0           $self->graph->set_edge_attribute("$buffer","$plug","port",$port);
226             }
227             else {
228 0           $self->graph->add_edge("$plug","$buffer");
229 0           $self->graph->set_edge_attribute("$plug","$buffer","port",$port);
230            
231             }
232 0           $self->{run_order} = undef;
233 0           return 1;
234             }
235              
236             sub cb_disconnect {
237 0     0 0   my ($self,$plug,$port) = @_;
238 0           my $buffer = $plug->get_buffer($port);
239 0 0         if ($buffer) {
240 0 0         if ($plug->is_input($port)) {
241 0           $self->graph->delete_edge($buffer,$plug);
242             }
243             else {
244 0           $self->graph->delete_edge($plug,$buffer);
245             }
246             }
247 0           $self->cleanup_buffers();
248 0           $self->{run_order} = undef;
249             }
250              
251             sub cleanup_buffers {
252 0     0 0   my ($self) = @_;
253 0           for ($self->buffers) {
254 0 0         $self->graph->delete_vertex($_) unless $self->graph->edges($_);
255             }
256             }
257              
258             sub connections {
259 0     0 1   my ($self,$plug,$port) = @_;
260 0 0         my $buffer = $plug->get_buffer($port) or croak "$plug has no buffer for port $port";
261 0           my @res;
262 0 0         if ($plug->is_input($port)) {
263 0           for ($self->graph->edges_to("$buffer")) {
264 0           push @res, $self->graph->get_vertex_attribute($_->[0],"plugin");
265 0           push @res, $self->graph->get_edge_attribute($_->[0],$_->[1],"port");
266             }
267             }
268             else {
269 0           for ($self->graph->edges_from("$buffer")) {
270 0           push @res, $self->graph->get_vertex_attribute($_->[1],"plugin");
271 0           push @res, $self->graph->get_edge_attribute($_->[0],$_->[1],"port");
272             }
273             }
274            
275 0           return @res;
276             }
277              
278             sub delete {
279 0     0 1   my ($self,$plugin) = @_;
280 0           $self->graph->delete_vertex($plugin);
281 0           $plugin->disconnect_all();
282 0           for ($plugin->ports) {
283 0           $self->notify_subscribers("disconnect",$plugin,$_);
284             }
285 0           delete $self->{plugin_by_uniqid}->{$plugin->get_uniqid};
286 0           $self->notify_subscribers("delete",$plugin);
287             }
288              
289             sub dump {
290 0     0 1   my ($self) = @_;
291             return {
292 0           'Audio::LADSPA::Network' => $VERSION,
293             'DumpVersion' => 0.01,
294 0           'Plugins' => [ map { $self->dump_plugin($_) } $self->plugins ],
295             'SampleRate' => $self->sample_rate,
296             'BufferSize' => $self->{buffer_size},
297             };
298             }
299              
300             sub dump_plugin {
301 0     0 0   my ($self,$plug) = @_;
302 0           my $dump = {
303             Class => ref($plug),
304             Ports => [],
305             Id => $plug->get_uniqid,
306             };
307 0           for ($plug->ports) {
308 0           my $port_dump = {
309             Name => $_,
310             };
311 0 0 0       if ($plug->is_control($_) && $plug->is_input($_)) {
312 0           $port_dump->{Value} = $plug->get_buffer($_)->get_1;
313             }
314 0 0         unless ($plug->is_input($_)) {
315 0           my @connections = $self->connections($plug,$_);
316 0           $port_dump->{Connections} = [];
317 0           while (my ($plug2,$port2) = splice(@connections,0,2)) {
318 0           push @{$port_dump->{Connections}}, {
  0            
319             Id => $plug2->get_uniqid,
320             Port => $port2 };
321             }
322             }
323 0           push @{$dump->{Ports}},$port_dump;
  0            
324             }
325 0           return $dump;
326             }
327              
328             sub from_dump {
329 0     0 1   my ($self,$dump) = @_;
330 0 0         croak "Not an Audio::LADSPA::Network dump" unless exists $dump->{'Audio::LADSPA::Network'};
331 0 0         croak "Incompatible dump version $dump->{Version} (must be < 1.0)" if $dump->{DumpVersion} >= 1.0;
332 0 0         if (ref $self) {
333 0 0         if ($self->sample_rate != $dump->{SampleRate}) {
334 0           warn "SampleRate from dump differs from current sample rate";
335             }
336 0 0         if ($self->{buffer_size} != $dump->{BufferSize}) {
337 0           warn "BufferSize from dump differs from current buffer size";
338             }
339             }
340             else {
341 0 0         $self = $self->new(
342             sample_rate => $dump->{SampleRate},
343             buffer_size => $dump->{BufferSize}
344             ) or croak "Cannot initialize new network";
345             }
346 0           my %plug_by_id;
347 0           for my $plugin_dump (reverse @{$dump->{Plugins}}) {
  0            
348            
349 0           my $plugin = $self->add_plugin(
350             $plugin_dump->{Class}->new(
351             $self->{sample_rate}, $plugin_dump->{Id}
352             )
353             );
354 0           $plug_by_id{$plugin_dump->{Id}} = $plugin;
355            
356            
357 0           for my $port_dump (@{$plugin_dump->{Ports}}) {
  0            
358 0           for my $remote (@{$port_dump->{Connections}}) {
  0            
359 0 0         $self->connect(
360             $plugin,
361             $port_dump->{Name},
362             $plug_by_id{$remote->{Id}},
363             $remote->{Port}
364             ) or croak "Cannot connect $plugin $port_dump->{Name} => $plug_by_id{$remote->{Id}} ($remote->{Id}) $remote->{Port}";
365             }
366 0 0         $plugin->set($port_dump->{Name},$port_dump->{Value}) if exists $port_dump->{Value};
367             }
368             }
369 0           return $self;
370             }
371              
372             sub plugin_by_uniqid {
373 0     0 1   my ($self,$uid) = @_;
374 0           return $self->{plugin_by_uniqid}->{$uid};
375             }
376              
377              
378              
379             1;
380              
381             __END__