rave.pl 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. #!/usr/bin/perl
  2. use strict;
  3. use warnings;
  4. use POSIX;
  5. use File::Touch;
  6. use File::Basename;
  7. use Time::HiRes qw( usleep sleep time );
  8. use IPC::System::Simple qw( system capture );
  9. use Sys::Hostname;
  10. # Local modules
  11. use Cwd qw(abs_path);
  12. use File::Basename qw(dirname);
  13. use lib dirname(abs_path($0));
  14. use DMX;
  15. use Audio;
  16. # Effect Prototypes
  17. sub red_alert($$$);
  18. sub lsr_init($$$);
  19. sub lsr_run($$$);
  20. sub lsr_loop($$$);
  21. # User config
  22. my $MEDIA_PATH = `~/bin/video/mediaPath` . '/DMX';
  23. my @CHANNELS = (1, 2, 4, 5, 6, 7, 8, 9, 13, 14, 15);
  24. my %EFFECTS = ();
  25. my %FILES = ();
  26. # Red Alert
  27. $FILES{'RED_ALERT'} = 'DMX/Red Alert.mp3';
  28. $EFFECTS{'RED_ALERT'} = { 'cmd' => \&red_alert };
  29. # Light-switch rave
  30. $EFFECTS{'LSR'} = { 'cmd' => \&lsr_init, 'next' => \&lsr_run, 'loop' => \&lsr_loop, 'done' => \&lsr_done };
  31. $FILES{'LSR'} = 'DMX/Rave.mp3';
  32. # Utility prototypes
  33. sub loadFiles($);
  34. sub ampWait($$$);
  35. # App config
  36. my $DATA_DIR = DMX::dataDir();
  37. my $STATE_SOCK = 'RAVE_CMD';
  38. my $OUTPUT_FILE = $DATA_DIR . $STATE_SOCK;
  39. my $RAVE_FILE = $DATA_DIR . 'RAVE';
  40. my $EFFECT_FILE = $DATA_DIR . 'EFFECT';
  41. my $PUSH_TIMEOUT = 20;
  42. my $PULL_TIMEOUT = $PUSH_TIMEOUT * 3;
  43. my $DELAY = $PULL_TIMEOUT / 2;
  44. my $AMP_DELAY = 6;
  45. my $AMP_BOOTING = 0;
  46. my $AMP_START = undef();
  47. my $AMP_TIMEOUT = $PUSH_TIMEOUT / 2;
  48. my $CLEANUP_TIME = undef();
  49. # Debug
  50. my $DEBUG = 0;
  51. if ($ENV{'DEBUG'}) {
  52. $DEBUG = 1;
  53. }
  54. # Load all defined audio files (which will kill us if they are not availble)
  55. loadFiles(\%FILES);
  56. # Sockets
  57. DMX::stateSocket($STATE_SOCK);
  58. DMX::stateSubscribe($STATE_SOCK);
  59. # State
  60. my %exists = ();
  61. my %last = ();
  62. my $pullLast = time();
  63. my $update = 0;
  64. my $PID = undef();
  65. my $NAME = undef();
  66. my $NEXT = undef();
  67. my $PID_DATA = undef();
  68. # Always clear the RAVE and EFFECT files
  69. if (-e $RAVE_FILE) {
  70. unlink($RAVE_FILE);
  71. }
  72. if (-e $EFFECT_FILE) {
  73. unlink($EFFECT_FILE);
  74. }
  75. # Loop forever
  76. while (1) {
  77. # State is transient during a RAVE
  78. my $newState = 'OFF';
  79. %last = %exists;
  80. # Reduce the select delay when we're processing background effects
  81. # We don't want to hang waiting for event updates while we're running the show
  82. my $delay = $DELAY;
  83. if (defined($PID)) {
  84. $delay = 0.001;
  85. } elsif (defined($NEXT)) {
  86. $delay = 0.25;
  87. }
  88. # Wait for state updates
  89. {
  90. my $cmdState = DMX::readState($delay, \%exists, undef(), undef());
  91. if (defined($cmdState)) {
  92. $newState = $cmdState;
  93. $pullLast = time();
  94. }
  95. }
  96. # Die if we don't see regular updates
  97. if (time() - $pullLast > $PULL_TIMEOUT) {
  98. die('No update on state socket in past ' . $PULL_TIMEOUT . " seconds. Exiting...\n");
  99. }
  100. # Handle "STOP" commands
  101. if ($newState eq 'STOP') {
  102. if (defined($PID)) {
  103. if ($DEBUG) {
  104. print STDERR "Ending background processing\n";
  105. }
  106. Audio::stopAll();
  107. kill(SIGTERM, $PID);
  108. }
  109. $update = 1;
  110. }
  111. # Reap zombie children
  112. if (defined($PID) && waitpid($PID, WNOHANG) > 0) {
  113. if ($DEBUG) {
  114. print STDERR 'Reaped child: ' . $PID . "\n";
  115. }
  116. $CLEANUP_TIME = Time::HiRes::time();
  117. }
  118. # Cleanup on timer or child exit
  119. if (defined($CLEANUP_TIME) && $CLEANUP_TIME <= Time::HiRes::time()) {
  120. # Run the cleanup routine (if any)
  121. if ($EFFECTS{$NAME}{'done'}) {
  122. $EFFECTS{$NAME}{'done'}($NAME, \%exists, $EFFECTS{$NAME});
  123. }
  124. # Forget our local bypass state
  125. $PID = undef();
  126. $CLEANUP_TIME = undef();
  127. $NAME = undef();
  128. $NEXT = undef();
  129. $PID_DATA = undef();
  130. # Clear the RAVE and EFFECT flags
  131. if (-e $RAVE_FILE) {
  132. unlink($RAVE_FILE);
  133. }
  134. if (-e $EFFECT_FILE) {
  135. unlink($EFFECT_FILE);
  136. }
  137. }
  138. # Continue special processing
  139. if (defined($NEXT)) {
  140. $NEXT->($NAME, \%exists, $EFFECTS{$NAME});
  141. next;
  142. }
  143. # Alert continuously on ALARM
  144. if ($exists{'ALARM'}) {
  145. $newState = 'RED_ALERT';
  146. }
  147. # Handle effects by name
  148. if (defined($EFFECTS{$newState})) {
  149. # Initiate the EFFECT state
  150. touch($EFFECT_FILE);
  151. # Store the effect name
  152. $NAME = $newState;
  153. # Dispatch the effect
  154. $EFFECTS{$NAME}{'cmd'}($NAME, \%exists, $EFFECTS{$NAME});
  155. # Clear the EFFECT state if there is no background process
  156. if (!defined($PID)) {
  157. if (-e $EFFECT_FILE) {
  158. unlink($EFFECT_FILE);
  159. }
  160. }
  161. next;
  162. }
  163. # Clear the RAVE and EFFECT flags, just in case
  164. if (-e $RAVE_FILE) {
  165. unlink($RAVE_FILE);
  166. die("Unexpected RAVE file\n");
  167. }
  168. if (-e $EFFECT_FILE) {
  169. unlink($EFFECT_FILE);
  170. die("Unexpected EFFECT file\n");
  171. }
  172. # Ensure our audio files are always loaded
  173. loadFiles(\%FILES);
  174. }
  175. # ======================================
  176. # Utility routines
  177. # ======================================
  178. sub loadFiles($) {
  179. my ($files) = @_;
  180. foreach my $file (keys(%{$files})) {
  181. if (!Audio::loaded($file)) {
  182. Audio::load($file, $files->{$file});
  183. }
  184. }
  185. }
  186. sub ampWait($$$) {
  187. my ($name, $exists, $params) = @_;
  188. if ($DEBUG) {
  189. print STDERR "ampWait()\n";
  190. }
  191. # Ensure we don't loop forever
  192. if (!defined($AMP_START)) {
  193. $AMP_START = Time::HiRes::time();
  194. } elsif (Time::HiRes::time() - $AMP_START > $AMP_TIMEOUT) {
  195. die("Timeout waiting for amp to start\n");
  196. }
  197. # Just loop until the amp is up, then set a new "next" handler
  198. if ($exists{'AMPLIFIER'}) {
  199. if ($DEBUG) {
  200. print STDERR "\tAmplifier ready\n";
  201. }
  202. # Once the amp is up, be sure we've started "RAVE" mode for audio output
  203. if ($exists{'AUDIO_STATE'} eq 'RAVE') {
  204. if ($DEBUG) {
  205. print STDERR "\tAudio device ready\n";
  206. }
  207. # Select the next function
  208. if ($params->{'next'}) {
  209. $NEXT = $params->{'next'};
  210. } else {
  211. $NEXT = undef();
  212. }
  213. # Always wait just a bit for good luck
  214. sleep($AMP_DELAY / 4);
  215. }
  216. # Wait for the amp to boot if it wasn't running when we first checked
  217. if ($AMP_BOOTING) {
  218. if ($DEBUG) {
  219. print STDERR "\tWaiting " . $AMP_DELAY . " seconds for amp to boot\n";
  220. }
  221. sleep($AMP_DELAY);
  222. }
  223. # Reset the amp boot delay and amp start time now that it's running
  224. $AMP_BOOTING = 0;
  225. undef($AMP_START);
  226. } else {
  227. # The amp wasn't up when we checked, so it will need a boot delay
  228. $AMP_BOOTING = 1;
  229. }
  230. # Do not pass GO, do not collect $200
  231. return 1;
  232. }
  233. # ======================================
  234. # Effects routines
  235. # ======================================
  236. sub red_alert($$$) {
  237. my ($name, $exists, $params) = @_;
  238. if ($DEBUG) {
  239. print STDERR "red_alert()\n";
  240. }
  241. my $ramp = 450;
  242. my $sleep = $ramp;
  243. # Bring the B & G channels down to 0
  244. my @other = ();
  245. push(@other, { 'channel' => 13, 'value' => 0, 'time' => 0 });
  246. push(@other, { 'channel' => 15, 'value' => 0, 'time' => 0 });
  247. foreach my $data (@other) {
  248. DMX::dim($data);
  249. }
  250. # Set the high/low values for the ramp
  251. my %high = ('channel' => 14, 'value' => 255, 'time' => $ramp);
  252. my %low = %high;
  253. $low{'value'} = 64;
  254. # Three blasts
  255. for (my $i = 0 ; $i < 3 ; $i++) {
  256. DMX::dim(\%high);
  257. Audio::play($name);
  258. DMX::dim(\%low);
  259. usleep($sleep * 1000);
  260. }
  261. # Follow through on the loop
  262. return 0;
  263. }
  264. sub lsr_init($$$) {
  265. my ($name, $exists, $params) = @_;
  266. if ($DEBUG) {
  267. print STDERR "lsr_init()\n";
  268. }
  269. # Initiate the RAVE state
  270. DMX::say('Light switch rave: initiated');
  271. touch($RAVE_FILE);
  272. # Save data for future runs
  273. my %data = ();
  274. $PID_DATA = \%data;
  275. # Initialize the channels hash
  276. my %chans = ();
  277. foreach my $chan (@CHANNELS) {
  278. $chans{$chan} = 0;
  279. }
  280. $data{'channels'} = \%chans;
  281. $data{'num_channels'} = scalar(keys(%chans));
  282. $data{'live_channels'} = 0;
  283. # Dim while we wait
  284. my @data_set = ();
  285. foreach my $chan (@CHANNELS) {
  286. push(@data_set, { 'channel' => $chan, 'value' => 0, 'time' => $AMP_DELAY * 1000 });
  287. }
  288. DMX::applyDataset(\@data_set, 'RAVE', $OUTPUT_FILE);
  289. # Wait for the amp to power up
  290. $NEXT = \&ampWait;
  291. # Do not pass GO, do not collect $200
  292. return 1;
  293. }
  294. sub lsr_run($$$) {
  295. my ($name, $exists, $params) = @_;
  296. if ($DEBUG) {
  297. print STDERR "lsr_run()\n";
  298. }
  299. # Wait just a second for annoucements
  300. sleep(1);
  301. # Play a short burst of silence to get all the audio devices in-sync
  302. Audio::play('SILENCE');
  303. # Play the sound in the background
  304. Audio::background($name);
  305. # Set a cleanup timer
  306. # Now + playback + fade + buffer
  307. $CLEANUP_TIME = Time::HiRes::time() + 45.60 + 2 + 1;
  308. # Record our start time
  309. $PID_DATA->{'start'} = Time::HiRes::time();
  310. # Start the main loop
  311. $NEXT = $params->{'loop'};
  312. # Do not pass GO, do not collect $200
  313. return 1;
  314. }
  315. sub lsr_loop($$$) {
  316. my ($name, $exists, $params) = @_;
  317. if ($DEBUG) {
  318. print STDERR "lsr_loop()\n";
  319. }
  320. # Config
  321. my $max_dur = 375;
  322. my $max_val = 255;
  323. my $reserve = 0.75;
  324. my $ramp_dur = 10.40;
  325. my $hit_pos = 43.35;
  326. my $hit_dur = 100;
  327. my $fade_pos = 43.85;
  328. my $fade_dur = 2000;
  329. # How long have we been playing
  330. my $elapsed = Time::HiRes::time() - $PID_DATA->{'start'};
  331. if ($DEBUG) {
  332. print STDERR 'Elapsed: ' . $elapsed . "\n";
  333. }
  334. # Ramp up the number of channels in our effect
  335. if ($elapsed < $ramp_dur) {
  336. my $ratio = $PID_DATA->{'live_channels'} / ($PID_DATA->{'num_channels'} * (1 - $reserve));
  337. if ($PID_DATA->{'live_channels'} < 1 || $ratio < $elapsed / $ramp_dur) {
  338. my @keys = keys(%{ $PID_DATA->{'channels'} });
  339. my $index = int(rand($PID_DATA->{'num_channels'}));
  340. while ($PID_DATA->{'channels'}->{ $keys[$index] } > 0) {
  341. $index = int(rand($PID_DATA->{'num_channels'}));
  342. }
  343. $PID_DATA->{'channels'}->{ $keys[$index] } = 1;
  344. $PID_DATA->{'live_channels'}++;
  345. }
  346. } else {
  347. if ($PID_DATA->{'live_channels'} < $PID_DATA->{'num_channels'}) {
  348. foreach my $chan (keys(%{ $PID_DATA->{'channels'} })) {
  349. $PID_DATA->{'channels'}->{$chan} = 1;
  350. }
  351. $PID_DATA->{'live_channels'} = $PID_DATA->{'num_channels'};
  352. }
  353. }
  354. # Prepare data for each channel
  355. my @data_set = ();
  356. my $wait = $max_dur;
  357. foreach my $chan (keys(%{ $PID_DATA->{'channels'} })) {
  358. my $val = 0;
  359. my $dur = 0;
  360. # Random data on enabled channels until the fade
  361. if ($elapsed < $hit_pos) {
  362. if ($PID_DATA->{'channels'}->{$chan} > 0) {
  363. $dur = int(rand($max_dur));
  364. $val = int(rand($max_val));
  365. }
  366. } elsif ($elapsed < $fade_pos) {
  367. $val = $max_val;
  368. $dur = $hit_dur;
  369. $wait = ($fade_pos - $hit_pos) * 1000;
  370. } else {
  371. $val = 0;
  372. $dur = $fade_dur;
  373. $wait = $dur;
  374. }
  375. push(@data_set, { 'channel' => $chan, 'value' => $val, 'time' => $dur });
  376. }
  377. # Push the data set
  378. DMX::applyDataset(\@data_set, 'RAVE', $OUTPUT_FILE);
  379. # Wait for the fade interval
  380. usleep($wait * 1000);
  381. }
  382. sub lsr_done($$$) {
  383. my ($name, $exists, $params) = @_;
  384. if ($DEBUG) {
  385. print STDERR "lsr_done()\n";
  386. }
  387. DMX::say('Light switch rave: terminated');
  388. }