1#!/usr/bin/env perl 2 3# Test script for communicating with audacity via mod-script-pipe 4# Audacity should be running first, with the scripting plugin loaded. 5# 6# Note that currently, some menu commands require the project to be focused for 7# them to work. Further information and a list of known problems is available 8# on the 'Scripting' page of the Audacity wiki. 9 10use strict; 11use warnings; 12use Time::HiRes qw( gettimeofday tv_interval ); 13use List::Util qw( max ); 14 15# Where should screenshots and exported tracks be saved? 16our $home = $ENV{HOME}; 17our $screenshotDir = $home.'/pipetest/'; 18our $effectTestDir = $home.'/pipetest/'; 19 20# Variables for pipe names 21our $Name; 22our $UID; 23our $ToSrvName; 24our $FromSrvName; 25 26# For timing 27our $t0; 28 29# TODO: Maybe get the pipe names from audacity? 30if ($^O eq 'MSWin32') { 31 $Name = 'Srv'; 32 $ToSrvName = '\\\\.\\pipe\\To'.$Name.'Pipe'; 33 $FromSrvName = '\\\\.\\pipe\\From'.$Name.'Pipe'; 34} elsif ($^O eq 'linux') { 35 $UID = $<; 36 $ToSrvName = '/tmp/audacity_script_pipe.to.'.$UID; 37 $FromSrvName = '/tmp/audacity_script_pipe.from.'.$UID; 38} elsif ($^O eq 'darwin') { 39 $UID = $<; 40 $ToSrvName = '/tmp/audacity_script_pipe.to.'.$UID; 41 $FromSrvName = '/tmp/audacity_script_pipe.from.'.$UID; 42} 43 44# Open pipes 45sub startUp{ 46 open( TO_SRV, "+<$ToSrvName" ) 47 or die "Could not open $ToSrvName"; 48 open( FROM_SRV, "+<$FromSrvName" ) 49 or die "Could not open $FromSrvName"; 50 51 # The next 'magic incantation' causes TO_SRV to be flushed every time we 52 # write something to it. 53 select((select(TO_SRV),$|=1)[0]); 54} 55 56# Close pipes 57sub finish{ 58 print "Done. Press return to end."; 59 <>; 60 close TO_SRV; 61 close FROM_SRV; 62} 63 64# Subroutines for measuring how long a command takes to complete 65sub startTiming{ 66 $t0 = [gettimeofday]; 67} 68sub stopTiming{ 69 my $elapsed = tv_interval ( $t0, [gettimeofday] ); 70 print "[Total time for command: $elapsed seconds.]\n"; 71} 72 73# Write a command to the pipe 74sub sendCommand{ 75 my $command = shift; 76 if ($^O eq 'MSWin32') { 77 print TO_SRV "$command 78 79\r\n\0"; 80 } else { 81 # Don't explicitly send \0 on Linux or reads after the first one fail... 82 print TO_SRV "$command\n"; 83 } 84 print "[$command]\n"; 85} 86 87# Send (and time) a command, and print responses 88sub doCommand{ 89 startTiming(); 90 sendCommand(shift); 91 92 my @resps = getResponses(); 93 map { print "$_\n"; } @resps; 94 95 stopTiming(); 96 print "\n"; 97 return @resps; 98} 99 100# Return an array of all responses 101sub getResponses{ 102 my $resp; 103 my @responses; 104 105 while($resp = <FROM_SRV>) { 106 chomp($resp); 107 last if ($resp eq ''); 108 push(@responses, $resp); 109 } 110 111 return @responses; 112} 113 114# Get the value of a preference 115sub getPref{ 116 my $name = shift; 117 sendCommand("GetPreference: PrefName=$name"); 118 my @resps = getResponses(); 119 return shift(@resps); 120} 121 122# Set the value of a preference 123sub setPref{ 124 my $name = shift; 125 my $val = shift; 126 doCommand("SetPreference: PrefName=$name PrefValue=$val"); 127} 128 129# Send a screenshot command 130sub screenshot{ 131 my $filePath = shift; 132 my $captureMode = shift; 133 my $background = shift; 134 doCommand("Screenshot: FilePath=$filePath CaptureMode=$captureMode Background=$background"); 135} 136 137# Send a menu command 138sub menuCommand{ 139 my $commandName = shift; 140 doCommand("MenuCommand: CommandName=$commandName"); 141} 142 143# Send a command which requests a list of all available menu commands 144sub getMenuCommands{ 145 doCommand("GetMenus: ShowStatus=0"); 146} 147 148sub showMenuStatus{ 149 sendCommand("GetMenus: ShowStatus=1"); 150 my @resps = getResponses(); 151 map { print "$_\n"; } @resps; 152} 153 154# Send a string that should be a syntax error 155sub syntaxError{ 156 doCommand("CommandWithNoColon foo bar"); 157} 158 159# Send a command that doesn't exist 160sub noSuchCommand{ 161 doCommand("NoSuchCommand: myParam=3"); 162} 163 164sub parameterTest{ 165 # Set a non-existent parameter 166 doCommand("GetMenus: blah=2"); 167 # Parameter with no '=' 168 doCommand("MenuCommand: CommandName"); 169} 170 171# See what happens when commands have extra spaces in various places 172sub extraSpaces{ 173 doCommand("Help: CommandName=Help"); 174 doCommand("Help : CommandName=Help"); 175 doCommand("Help: CommandName =Help"); 176 doCommand("Help: CommandName= Help"); 177 doCommand("Help: CommandName=Help "); 178} 179 180# Test whether we can fall back to batch mode 181sub batchFallback{ 182 doCommand( "Echo: Delay=1.0 Decay=0.5" ); 183} 184 185# Send lots of commands quickly 186sub stressTest{ 187 my $n = 0; 188 while($n < 600){ 189 getMenuCommands(); 190 ++$n; 191 } 192} 193 194# Get help on a command 195sub help{ 196 my $cmdName = shift; 197 doCommand("Help: CommandName=$cmdName"); 198} 199 200# Get help on all of the listed commands 201sub fullHelp{ 202 my @cmds = qw(BatchCommand CompareAudio MenuCommand GetMenus GetTrackInfo Help Message Screenshot Select SetTrackInfo); 203 foreach my $cmd (@cmds){ 204 help($cmd); 205 } 206} 207 208# From script, this works like an 'echo' 209sub message{ 210 my $msg = shift; 211 doCommand("Message: MessageString=$msg"); 212} 213 214# Send a CompareAudio command with a given threshold 215sub compareAudio{ 216 my $threshold = shift; 217 my @resps = doCommand("CompareAudio: Threshold=$threshold"); 218 shift(@resps); 219 return @resps; 220} 221 222# Delete all tracks 223sub deleteAll{ 224 doCommand("Select: Mode=All"); 225 menuCommand("RemoveTracks"); 226} 227 228# A test of the CompareAudio command 229sub compareTest{ 230 deleteAll(); 231 232 menuCommand("NewMonoTrack"); 233 doCommand("Chirp:"); 234 menuCommand("NewMonoTrack"); 235 doCommand("Chirp:"); 236 237 my $j = 0; 238 while($j < 3) 239 { 240 my $i = 0; 241 while($i < 6){ 242 doCommand("Select: Mode=Range StartTime=5.0 EndTime=8.0 FirstTrack=0 LastTrack=0"); 243 244 doCommand("Amplify: Ratio=0.95"); 245 doCommand("Select: Mode=All"); 246 compareAudio(0.4 - 0.1*$j); 247 ++$i; 248 } 249 ++$j; 250 } 251} 252 253# Print some info returned by the GetTrackInfo command 254sub getTrackInfo{ 255 my $trackID = shift; 256 sendCommand("GetTrackInfo: Type=Name TrackIndex=0"); 257 my @resps = getResponses(); 258 my $name = shift(@resps); 259 sendCommand("GetTrackInfo: Type=StartTime TrackIndex=0"); 260 @resps = getResponses(); 261 my $startTime = shift(@resps); 262 sendCommand("GetTrackInfo: Type=EndTime TrackIndex=0"); 263 @resps = getResponses(); 264 my $endTime = shift(@resps); 265 sendCommand("GetTrackInfo: Type=Pan TrackIndex=0"); 266 @resps = getResponses(); 267 my $pan = shift(@resps); 268 sendCommand("GetTrackInfo: Type=Gain TrackIndex=0"); 269 @resps = getResponses(); 270 my $gain = shift(@resps); 271 sendCommand("GetTrackInfo: Type=Mute TrackIndex=0"); 272 @resps = getResponses(); 273 my $mute = shift(@resps); 274 sendCommand("GetTrackInfo: Type=Solo TrackIndex=0"); 275 @resps = getResponses(); 276 my $solo = shift(@resps); 277 sendCommand("GetTrackInfo: Type=Selected TrackIndex=0"); 278 @resps = getResponses(); 279 my $selected = shift(@resps); 280 sendCommand("GetTrackInfo: Type=Focused TrackIndex=0"); 281 @resps = getResponses(); 282 my $focused = shift(@resps); 283 sendCommand("GetTrackInfo: Type=Linked TrackIndex=0"); 284 @resps = getResponses(); 285 my $linked = shift(@resps); 286 287 print " Name: $name\n"; 288 print "StartTime: $startTime\n"; 289 print " EndTime: $endTime\n"; 290 print " Pan: $pan\n"; 291 print " Gain: $gain\n"; 292 print " Mute: $mute\n"; 293 print " Solo: $solo\n"; 294 print " Selected: $selected\n"; 295 print " Focused: $focused\n"; 296 print " Linked: $linked\n"; 297} 298 299# Assortment of different tests 300sub fullTest{ 301 syntaxError(); 302 extraSpaces(); 303 menuCommand("NewStereoTrack"); 304 #screenshot($screenshotDir, "window", "None"); # (Slow) 305 doCommand("Select: Mode=All"); 306 getMenuCommands(); 307 menuCommand("NewMonoTrack"); 308 batchFallback(); 309 help("Screenshot"); 310 message("Hello!"); 311 getTrackInfo(0); 312 deleteAll(); 313} 314 315# Play for three seconds, then stop 316sub playAndStop{ 317 menuCommand("Play"); 318 sleep(3.0); 319 menuCommand("Stop"); 320} 321 322# Select part of a stereo track 323sub selectRegion{ 324 my $track = shift; 325 my $start = shift; 326 my $end = shift; 327 my $t1 = $track + 1; 328 doCommand("Select: Mode=Range FirstTrack=$track LastTrack=$t1 StartTime=$start EndTime=$end"); 329} 330 331# Run testing on the effects that use the ClearAndPaste method 332# Allows the user to check whether effects transform time correctly 333sub testClearAndPasters{ 334 335 # Which effects to test, and with what parameters 336 my @clearAndPasters = ( 337 "Unchanged:", # control: nonexistent command, so does nothing 338 # (so 'batch command not recognised' isn't an error) 339 "Noise:", # generate 340 "NoiseRemoval:", # misc clear&paste 341 "ChangeSpeed: Percentage=-10.0", # misc clear&paste 342 "ChangeSpeed: Percentage=40.0", # misc clear&paste 343 "ChangeTempo: Percentage=-20.0", # soundtouch 344 "ChangeTempo: Percentage=80.0", # soundtouch 345 "ChangePitch: Percentage=25.0", # soundtouch 346 "ChangePitch: Percentage=-80.0", # soundtouch 347 "TimeScale: RateStart=-80.0 RateEnd=150.0 HalfStepsStart=-5.0 HalfStepsEnd=8.0 PreAnalyze=no", # SBSMS 348 ); # nyquist can't be called currently 349 350 # Allow time for user to give the project window focus (workaround for menu 351 # command problem) 352 sleep(1.0); 353 deleteAll(); 354 my $len = 20.0; 355 356 # Since there aren't proper generator commands yet, we use the preferences 357 # to control the duration. 358 # This preferences is not read in Audacity 2.2.x where duration 359 # is read from pluginsettings.cfg 360 my $origDuration = getPref("/CsPresets/NoiseGen_Duration"); 361 setPref("/CsPresets/NoiseGen_Duration", $len); 362 363 # For each effect to test: 364 # * Create some stereo noise, and chop two pieces out of it 365 # * Add some labels, then apply the effect 366 # @splits determines where the splits are 367 my @splits = map {$_ * $len} (0.999, 0.2, 0.5, 0.6, 0.8, 0.1, 0.9); 368 my $trackNum = 0; 369 foreach my $effect (@clearAndPasters) { 370 menuCommand("NewStereoTrack"); 371 selectRegion($trackNum, 0.0, $splits[0]); 372 doCommand("Noise:"); 373 selectRegion($trackNum, $splits[1], $splits[2]); 374 menuCommand("SplitDelete"); 375 menuCommand("AddLabel"); 376 selectRegion($trackNum, $splits[3], $splits[4]); 377 menuCommand("SplitDelete"); 378 menuCommand("AddLabel"); 379 380 # Apply the effect 381 selectRegion($trackNum, $splits[5], $splits[6]); 382 doCommand($effect); 383 384 # Make and set the track name 385 my @splat = split(':', $effect); 386 my $name = $splat[0]; 387 doCommand("SetTrackInfo: TrackIndex=$trackNum Type=Name Name=$name"); 388 doCommand("Select: Mode=None"); 389 390 $trackNum = $trackNum + 3; 391 } 392 393 # Set duration back to what it was before 394 setPref("/CsPresets/NoiseGen_Duration", $origDuration); 395} 396 397 398############################################################################### 399# Effect testing # 400############################################################################### 401 402# A list of effects to test (could be got from Audacity in future) 403sub getEffects{ 404 405 # (These ones will need special handling) 406 # AutoDuck 407 # Bass and Treble 408 # Repair 409 # NoiseRemoval 410 # ClickRemoval 411 # Paulstretch 412 413 # TimeScale (disabled because it's so slow) 414 415 my @effects = qw( 416 Amplify 417 ChangePitch 418 ChangeSpeed 419 ChangeTempo 420 Compressor 421 Echo 422 Equalization 423 FadeIn 424 FadeOut 425 Invert 426 Normalize 427 Phaser 428 Repeat 429 Reverse 430 TruncateSilence 431 Wahwah 432 ); 433 return @effects; 434} 435 436# Create a chirp for an effect to be applied to. 437# The duration setting does not work in Audacity 2.2.x where duration 438# is read from pluginsettings.cfg 439sub generateBase{ 440 my $genCmd = "Chirp"; 441 my $duration = 30.0; 442 menuCommand("NewMonoTrack"); 443 doCommand("$genCmd:"); 444 my $desc = $genCmd . "-" . $duration . "s"; 445 return $desc; 446} 447 448# Apply an effect and save the results (for use as reference output) 449sub saveEffectResults{ 450 my $dirname = shift; 451 my $effect = shift; 452 deleteAll(); 453 454 my $filename = $dirname . "/" . generateBase() . "-" . $effect . ".wav"; 455 doCommand($effect); 456 457 printHeading("Exporting to $filename\n"); 458 doCommand("Export: Mode=All Filename=$filename Channels=1"); 459} 460 461# Apply an effect and compare the result to reference output 462sub doEffectTest{ 463 my $dirname = shift; 464 my $effect = shift; 465 466 deleteAll(); 467 my $filename = $dirname . "/" . generateBase() . "-" . $effect . ".wav"; 468 doCommand("SetTrackInfo: TrackIndex=0 Type=Name Name=$effect"); 469 doCommand($effect); 470 doCommand("Import: Filename=$filename"); 471 doCommand("Select: Mode=All"); 472 my @result = compareAudio(0.001); 473 return @result; 474} 475 476# Export reference copies of the effects in the list 477sub exportEffects{ 478 my $exportDir = shift; 479 my @effects = getEffects(); 480 foreach my $effect (@effects) { 481 saveEffectResults($exportDir, $effect); 482 } 483} 484 485# Test each of the effects in the list 486sub testEffects{ 487 my $referenceDir = shift; 488 my %results = (); 489 490 my @effects = getEffects(); 491 foreach my $effect (@effects) { 492 printHeading("Testing effect: $effect"); 493 my @res = doEffectTest($referenceDir, $effect); 494 $results{ $effect }[0] = $res[0]; 495 $results{ $effect }[1] = $res[1]; 496 } 497 498 # Print out table of results 499 my $padLength = max(map { length($_) } @effects); 500 501 printHeading("Test results"); 502 print "Effect name\tSamples\tSeconds\n\n"; 503 for my $effect (keys %results) { 504 my $padded = sprintf("%-${padLength}s", $effect); 505 my $badSamples = $results{ $effect }[0]; 506 my $badSeconds = $results{ $effect }[1]; 507 print "$padded\t$badSamples\t$badSeconds\n"; 508 } 509} 510 511# Print text with ascii lines above and below 512sub printHeading{ 513 my $msg = shift; 514 my $line = "-" x length($msg); 515 print "$line\n$msg\n$line\n\n"; 516} 517 518############################################################################### 519 520startUp(); 521 522# Send some test commands 523 524exportEffects($effectTestDir); 525testEffects($effectTestDir); 526 527finish(); 528