rave.pl 9.6 KB

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