- commit
- c3f635e
- parent
- e87a3f6
- author
- Neale Pickett
- date
- 2016-09-25 21:51:15 -0600 MDT
Now with classes, debugging music+lights=gaps
5 files changed,
+207,
-305
+72,
-0
1@@ -0,0 +1,72 @@
2+#include <Arduino.h>
3+#include <SPI.h>
4+#include <Adafruit_VS1053.h>
5+#include <SD.h>
6+#include "MusicPlayer.h"
7+
8+
9+
10+MusicPlayer::MusicPlayer(int8_t cs, int8_t dcs, int8_t dreq, int8_t cardcs)
11+{
12+ musicPlayer = new Adafruit_VS1053_FilePlayer(cs, dcs, dreq, cardcs);
13+ musicPlayer->begin();
14+ musicPlayer->setVolume(20, 20); // lower = louder
15+ musicPlayer->sineTest(0x44, 500);
16+ SD.begin(cardcs);
17+}
18+
19+void
20+MusicPlayer::setVolume(uint8_t left, uint8_t right)
21+{
22+ musicPlayer->setVolume(left, right);
23+}
24+
25+
26+boolean
27+MusicPlayer::startPlayingFile(const char *trackname)
28+{
29+ return musicPlayer->startPlayingFile(trackname);
30+}
31+
32+void
33+MusicPlayer::stopPlaying()
34+{
35+ musicPlayer->stopPlaying();
36+}
37+
38+void
39+MusicPlayer::poll(unsigned long jiffies)
40+{
41+ /* Cleverness ensues
42+ *
43+ * The Adafruit library is written to let you go off and do whatever you need,
44+ * hooking into an interrupt to sort of act like a multitasking operating system,
45+ * interrupting your program periodically.
46+ *
47+ * That's smart, since it makes it easy to use,
48+ * but we want this to be responsive, and can't handle something barging in and taking up lots of time:
49+ * it makes things look really uneven as our display code pauses to fill the buffer.
50+ * Fortunately, we don't have to fill the entire buffer at once, we can trickle data in.
51+ * That's what this does.
52+ *
53+ * Since the entire program is polling, without ever calling delay,
54+ * and hopefully doing what needs to be done quickly,
55+ * we check to see if the music chip wants more data.
56+ * If it does, we give it one chunk, and only one chunk,
57+ * rather than filling its buffer back up completely.
58+ *
59+ * There is still some weirdness with this loop,
60+ * possibly because the SPI routines are masking interrupts used to increment millis.
61+ * But it's remarkably more fluid than the other way.
62+ */
63+
64+ if (musicPlayer->playingMusic && musicPlayer->readyForData()) {
65+ int bytesread = musicPlayer->currentTrack.read(musicPlayer->mp3buffer, VS1053_DATABUFFERLEN);
66+ if (bytesread == 0) {
67+ musicPlayer->playingMusic = false;
68+ musicPlayer->currentTrack.close();
69+ } else {
70+ musicPlayer->playData(musicPlayer->mp3buffer, bytesread);
71+ }
72+ }
73+}
+14,
-0
1@@ -0,0 +1,14 @@
2+#pragma once
3+#include <Arduino.h>
4+#include <SD.h>
5+#include <Adafruit_VS1053.h>
6+
7+class MusicPlayer {
8+ Adafruit_VS1053_FilePlayer *musicPlayer;
9+public:
10+ MusicPlayer(int8_t cs, int8_t dcs, int8_t dreq, int8_t cardcs);
11+ boolean startPlayingFile(const char *trackname);
12+ void setVolume(uint8_t left, uint8_t right);
13+ void stopPlaying();
14+ void poll(unsigned long jiffies); // Call this once per loop()
15+};
+39,
-305
1@@ -3,343 +3,77 @@
2
3 #include <SPI.h>
4 #include <Wire.h>
5-#include <SD.h>
6 #include <Adafruit_NeoPixel.h>
7-#include <Adafruit_VS1053.h>
8 #include <Adafruit_LEDBackpack.h>
9 #include <Adafruit_GFX.h>
10+#include "MusicPlayer.h"
11+#include "Synchrotron.h"
12
13-#define DEBUG 12
14+// Music Player
15+#define MUSIC_CS 7
16+#define MUSIC_DATA 6
17+#define MUSIC_CARDCS 4
18+#define MUSIC_REQ 3
19+MusicPlayer *music;
20
21-// Music Player object
22-#define SHIELD_RESET -1 // VS1053 reset pin (unused!)
23-#define SHIELD_CS 7 // VS1053 chip select pin (output)
24-#define SHIELD_DCS 6 // VS1053 Data/command select pin (output)
25-#define CARDCS 4 // Card chip select
26-#define DREQ 1 // VS1053 Data request (an interrupt pin)
27-Adafruit_VS1053_FilePlayer musicPlayer = Adafruit_VS1053_FilePlayer(SHIELD_RESET, SHIELD_CS, SHIELD_DCS, DREQ, CARDCS);
28+// Synchrotron
29+#define SYNC1_NPIXELS 24
30+#define SYNC1_DATA 5
31+Synchrotron *sync1;
32
33-// NeoPixel: so cool
34-#define SYNCHROTRON_PIN 5
35-#define SYNCHROTRON_PIXELS 24 // I'm using the middle-sized NeoPixel ring
36-Adafruit_NeoPixel synchrotron = Adafruit_NeoPixel(SYNCHROTRON_PIXELS, SYNCHROTRON_PIN, NEO_GRB | NEO_KHZ800);
37-
38-// 7-segment displays
39-Adafruit_7segment disp1 = Adafruit_7segment();
40+// Debug LED
41+#define DEBUG 13
42
43 // Inputs
44-#define TRIGGER 8
45-
46-// Nominal brightness
47-#define brightness 64
48+#define TRIGGER 9
49
50-const byte dispBright = 10;
51+// global time counter
52 unsigned long jiffies = 0;
53
54-void rgbPWM(byte r, byte g, byte b) {
55- // XXX: do this
56-}
57-
58-void rgb(byte r, byte g, byte b) {
59- for (int i = 0; i < SYNCHROTRON_PIXELS; i += 1) {
60- synchrotron.setPixelColor(i, synchrotron.Color(r, g, b));
61- }
62- synchrotron.show();
63-}
64-
65 void setup() {
66 randomSeed(analogRead(12));
67
68- // synchrotron
69- synchrotron.begin();
70- synchrotron.show(); // Turn everything off
71-
72 // inputs
73 pinMode(TRIGGER, INPUT_PULLUP);
74
75- // music player, this sets up SPI for us
76- SD.begin(CARDCS);
77- musicPlayer.begin();
78- musicPlayer.setVolume(20, 20); // lower = louder
79- // We don't set useInterrupt, since we do our own polling for smoother operations
80-
81- // 7-segment displays.
82- // These also use SPI, in i2c mode.
83- // Since the music player has a CS line,
84- // and we're unlikely to send the right i2c command to the 7-segment to wake it up,
85- // it's okay to use the same SPI bus for both.
86- disp1.begin(0x70);
87-}
88-
89-// Synchrotron needs to "spin up"
90-// We start slow, with red, then work our way through the rainbow to blue
91-bool charge() {
92- static uint32_t count = 0;
93- static int every = 9;
94- static int reps = 0;
95- uint32_t color_count;
96- byte r, g, b;
97- static int whichout = 0;
98-
99- // Play startup sound at the start
100- if (count == 0) {
101- musicPlayer.startPlayingFile("track001.mp3");
102- }
103-
104- // Make the animation play out a little more slowly,
105- // while still allowing a nice fast rotation
106- color_count = count / 4;
107-
108- // Give the illusion of something spinning up
109- if (every == 1) {
110- whichout = (whichout + 1) % SYNCHROTRON_PIXELS;
111- } else if (count % every == 0) {
112- whichout = (whichout + 1) % SYNCHROTRON_PIXELS;
113- reps += 1;
114- if (reps == 20 - every) {
115- every -= 1;
116- reps = 0;
117- }
118- }
119-
120- // Start at blue, go through hue to red
121- switch (color_count / brightness) {
122- case 0:
123- r = color_count % brightness;
124- g = 0;
125- b = 0;
126- break;
127- case 1:
128- r = brightness - (color_count % brightness) - 1;
129- g = color_count % brightness;
130- b = 0;
131- break;
132- case 2:
133- r = 0;
134- g = brightness - (color_count % brightness) - 1;
135- b = color_count % brightness;
136- break;
137- default:
138- rgb(brightness, 0, 0);
139- return true;
140- }
141-
142- // Set 'em up pixels
143- for (int i = 0; i < SYNCHROTRON_PIXELS; i += 1) {
144- if (whichout == i) {
145- synchrotron.setPixelColor(i, 0);
146- } else if ((whichout == (i+1) % SYNCHROTRON_PIXELS) || ((whichout+1) % SYNCHROTRON_PIXELS == i)) {
147- synchrotron.setPixelColor(i, synchrotron.Color(r/4, g/4, b/4));
148- } else {
149- synchrotron.setPixelColor(i, synchrotron.Color(r, g, b));
150- }
151- }
152- synchrotron.show();
153-
154- disp1.clear();
155- disp1.printNumber(0xb00, HEX);
156- disp1.setBrightness(dispBright);
157- disp1.writeDisplay();
158-
159- count += 1;
160-
161- return false;
162-}
163-
164-// Do a sort of mirrored KITT effect
165-bool kitt() {
166- static int count = 0;
167- int out = count % (SYNCHROTRON_PIXELS/2);
168-
169- if (jiffies % 12 != 0) {
170- return false;
171- }
172-
173- for (int i = 0; i < SYNCHROTRON_PIXELS; i += 1) {
174- int pixnum = (SYNCHROTRON_PIXELS/2) - abs(i - (SYNCHROTRON_PIXELS / 2));
175- int intensity;
176-
177- if (count < SYNCHROTRON_PIXELS/2) {
178- intensity = 100;
179- if (pixnum == out) {
180- intensity = 50;
181- } else if (pixnum < out) {
182- intensity = 10;
183- }
184- } else {
185- intensity = 10;
186- if (pixnum == out) {
187- intensity = 50;
188- } else if (pixnum < out) {
189- intensity = 100;
190- }
191- }
192- synchrotron.setPixelColor(i, synchrotron.Color(brightness * intensity / 100, 0, 0));
193- }
194- synchrotron.show();
195+ // outputs
196+ pinMode(DEBUG, OUTPUT);
197
198- count += 1;
199- if (count > SYNCHROTRON_PIXELS) {
200- rgb(brightness, 0, 0);
201- count = 0;
202- return true;
203- }
204- return false;
205-}
206-
207-bool glitch(int r, int g, int b) {
208- static int state = 0;
209- int i;
210-
211- if (jiffies % 10 != 0) {
212- return false;
213- }
214-
215- switch (state) {
216- case 0:
217- // glitch to a random color
218- r = random(brightness / 6);
219- g = random(brightness / 6);
220- b = random(brightness / 6);
221- rgb(r, g, b);
222- state = 1;
223- break;
224- case 1:
225- rgb(r, g, b);
226- state = 0;
227- return true;
228- break;
229- }
230-
231- return false;
232-}
233+ // music player, this sets up SPI for us
234+ music = new MusicPlayer(MUSIC_CS, MUSIC_DATA, MUSIC_REQ, MUSIC_CARDCS);
235
236-void fire() {
237- rgb(0, brightness, brightness);
238+ // synchrotron
239+ sync1 = new Synchrotron(SYNC1_NPIXELS, SYNC1_DATA);
240 }
241
242-void fireDone() {
243- rgb(brightness, 0, 0);
244-}
245
246 void flashDebug() {
247- if (jiffies % 50 == 0) {
248- int val = digitalRead(DEBUG);
249- digitalWrite(DEBUG, (val==HIGH)?LOW:HIGH);
250- }
251-}
252-
253-void tick() {
254- static int doing = 0;
255- static float val1 = 584.2;
256- static bool firing = false;
257- bool trigger;
258+ uint8_t val;
259
260- trigger = (digitalRead(TRIGGER) == LOW);
261-
262- if (trigger) {
263- firing = true;
264- doing = 100;
265- }
266-
267- switch (doing) {
268- case 0: // doing nothing
269- if (jiffies % 300 == 0) {
270- doing = 1; // KITT
271- } else if (random(350) == 0) {
272- doing = 2; // surge
273- } else if (random(400) == 0) {
274- doing = 3; // glitch
275- }
276- break;
277- case 1:
278- if (kitt()) {
279- doing = 0;
280- }
281- break;
282- case 2:
283- doing = 0;
284- break;
285- case 3:
286- if (glitch(brightness, 0, 0)) {
287- doing = 0;
288- }
289- break;
290- case 100:
291- fire();
292- if (! trigger) {
293- doing = 101;
294- }
295- break;
296- case 101:
297- fireDone();
298- doing = 0;
299- break;
300- default:
301- doing = 0;
302- }
303-
304- // screw around with the displays
305- if (random(20) == 0) {
306- val1 += (random(3) - 1) / 10.0;
307- disp1.print(val1);
308- disp1.setBrightness(dispBright);
309- disp1.writeDisplay();
310- } else if (random(150) == 0) {
311- disp1.setBrightness(random(16));
312- disp1.writeDisplay();
313- } else if (random(150) == 0) {
314- disp1.clear();
315- disp1.writeDisplay();
316- } else if (random(400) == 0) {
317- int someNumber = random(9999);
318- disp1.print(someNumber);
319- disp1.writeDisplay();
320- }
321-
322- flashDebug();
323+ val = (jiffies % 100) < 50;
324+ digitalWrite(DEBUG, val);
325 }
326
327 void loop() {
328+ static int state = 0;
329 // 6 seems to be about what my overly-critical brain needs to buffer out
330 // any music player delays so that they're unnoticeable
331 unsigned long new_jiffies = millis() / 6;
332+ boolean trigger = ! digitalRead(TRIGGER);
333
334- if (new_jiffies > jiffies) {
335- jiffies = new_jiffies;
336- tick();
337- }
338+ music->poll(jiffies);
339+
340+ if (state == 0) {
341+ if (new_jiffies > jiffies) {
342+ if (trigger) {
343+ state = 1;
344+ music->startPlayingFile("track001.mp3");
345+ sync1->charge();
346+ }
347
348- /* Cleverness ensues
349- *
350- * The Adafruit library is written to let you go off and do whatever you need,
351- * hooking into an interrupt to sort of act like a multitasking operating system,
352- * interrupting your program periodically.
353- *
354- * That's smart, since it makes it easy to use,
355- * but we want this to be responsive, and can't handle something barging in and taking up lots of time:
356- * it makes things look really uneven as our display code pauses to fill the buffer.
357- * Fortunately, we don't have to fill the entire buffer at once, we can trickle data in.
358- * That's what this does.
359- *
360- * Since the entire program is polling, without ever calling delay,
361- * and hopefully doing what needs to be done quickly,
362- * we check to see if the music chip wants more data.
363- * If it does, we give it one chunk, and only one chunk,
364- * rather than filling its buffer back up completely.
365- *
366- * There is still some weirdness with this loop,
367- * possibly because the SPI routines are masking interrupts used to increment millis.
368- * But it's remarkably more fluid than the other way.
369- */
370-
371- if (musicPlayer.playingMusic && musicPlayer.readyForData()) {
372- int bytesread = musicPlayer.currentTrack.read(musicPlayer.mp3buffer, VS1053_DATABUFFERLEN);
373- if (bytesread == 0) {
374- musicPlayer.playingMusic = false;
375- musicPlayer.currentTrack.close();
376- } else {
377- musicPlayer.playData(musicPlayer.mp3buffer, bytesread);
378+ jiffies = new_jiffies;
379+ sync1->tick(jiffies);
380+ flashDebug();
381 }
382 }
383 }
+62,
-0
1@@ -0,0 +1,62 @@
2+#include <Arduino.h>
3+#include <Adafruit_NeoPixel.h>
4+#include "Synchrotron.h"
5+
6+#define brightness 255
7+
8+Synchrotron::Synchrotron(uint16_t n, uint8_t p, neoPixelType t)
9+{
10+ pxl = new Adafruit_NeoPixel(n, p, t);
11+ npixels = n;
12+ cur = 0;
13+ pxl->begin();
14+ pxl->show();
15+ standby();
16+}
17+
18+Synchrotron::standby() {
19+ tickrate = 12;
20+ ticks = 0;
21+ r = brightness;
22+ g = 0;
23+ b = 0;
24+}
25+
26+Synchrotron::charge() {
27+ tickrate = 2;
28+ ticks = 0;
29+ r = brightness;
30+ g = brightness / 8;
31+ b = 0;
32+}
33+
34+Synchrotron::fire() {
35+}
36+
37+Synchrotron::discharge() {
38+}
39+
40+Synchrotron::tick(unsigned long jiffies) {
41+ float adj = (float)ticks / (float)tickrate;
42+ byte raa = r * adj;
43+ byte gaa = g * adj;
44+ byte baa = b * adj;
45+ byte ra = r - raa;
46+ byte ga = g - gaa;
47+ byte ba = b - baa;
48+
49+ pxl->clear();
50+ pxl->setPixelColor((cur + 1) % npixels, pxl->Color(raa, gaa, baa));
51+ for (int i = 0; i < 4; i += 1) {
52+ int div = 1 << (2*i);
53+ pxl->setPixelColor((cur + npixels - i) % npixels, pxl->Color(ra/div, ga/div, ba/div));
54+ }
55+
56+ pxl->show();
57+
58+ ticks += 1;
59+ if (ticks == tickrate) {
60+ ticks = 0;
61+ cur = (cur + 1) % npixels;
62+ }
63+}
+20,
-0
1@@ -0,0 +1,20 @@
2+#pragma once
3+#include <Arduino.h>
4+#include <Adafruit_NeoPixel.h>
5+
6+
7+class Synchrotron {
8+ Adafruit_NeoPixel *pxl;
9+ uint16_t npixels; // How many pixels there are
10+ int cur; // Which pixel the synchrotron is on, currently
11+ int tickrate; // How many millis between pixel position changes
12+ int ticks; // How many ticks have elapsed since last position change
13+ byte r, g, b; // Current color
14+public:
15+ Synchrotron(uint16_t n, uint8_t p=6, neoPixelType t=NEO_GRB + NEO_KHZ800);
16+ standby();
17+ charge();
18+ fire();
19+ discharge();
20+ tick(unsigned long jiffies); // Try to call this every jiffy
21+};