Skip to content

Commit 64707d7

Browse files
Add new, simplified, reactive Out Of Box Experience. (#74)
1 parent f4aaca3 commit 64707d7

File tree

3 files changed

+323
-1
lines changed

3 files changed

+323
-1
lines changed

source/samples/OOB.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -858,7 +858,7 @@ void onFacePalm(MicroBitEvent e) {
858858
}
859859

860860
void
861-
out_of_box_experience()
861+
out_of_box_experience_v2()
862862
{
863863
target_freq = 0;
864864
current_freq = 0;

source/samples/OOB_v3.cpp

Lines changed: 321 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,321 @@
1+
#include "MicroBit.h"
2+
#include "Synthesizer.h"
3+
#include "StreamRecording.h"
4+
#include "LowPassFilter.h"
5+
#include "Tests.h"
6+
7+
const char * const heart =
8+
"000,255,000,255,000\n"
9+
"255,255,255,255,255\n"
10+
"255,255,255,255,255\n"
11+
"000,255,255,255,000\n"
12+
"000,000,255,000,000\n";
13+
static const MicroBitImage HEART(heart);
14+
15+
const char * const happy =
16+
"000,000,000,000,000\n"
17+
"000,255,000,255,000\n"
18+
"000,000,000,000,000\n"
19+
"255,000,000,000,255\n"
20+
"000,255,255,255,000\n";
21+
static const MicroBitImage HAPPY(happy);
22+
23+
const char * const sad =
24+
"000,000,000,000,000\n"
25+
"000,255,000,255,000\n"
26+
"000,000,000,000,000\n"
27+
"000,255,255,255,000\n"
28+
"255,000,000,000,255\n";
29+
static const MicroBitImage SAD(sad);
30+
31+
const char * const silent =
32+
"000,255,000,255,000\n"
33+
"000,000,000,000,000\n"
34+
"000,000,000,000,000\n"
35+
"255,255,255,255,255\n"
36+
"000,000,000,000,000\n";
37+
static const MicroBitImage SILENT(silent);
38+
39+
const char * const singing =
40+
"000,255,000,255,000\n"
41+
"000,000,000,000,000\n"
42+
"000,255,255,255,000\n"
43+
"255,000,000,000,255\n"
44+
"000,255,255,255,000\n";
45+
static const MicroBitImage SINGING(singing);
46+
47+
const char * const asleep =
48+
"000,000,000,000,000\n"
49+
"255,255,000,255,255\n"
50+
"000,000,000,000,000\n"
51+
"000,255,255,255,000\n"
52+
"000,000,000,000,000\n";
53+
static const MicroBitImage ASLEEP(asleep);
54+
55+
const char * const sun =
56+
"255,000,255,000,255\n"
57+
"000,255,255,255,000\n"
58+
"255,255,255,255,255\n"
59+
"000,255,255,255,000\n"
60+
"255,000,255,000,255\n";
61+
static const MicroBitImage SUN(sun);
62+
63+
const char * const moon =
64+
"000,000,255,255,000\n"
65+
"000,000,000,255,255\n"
66+
"000,000,000,255,255\n"
67+
"000,000,000,255,255\n"
68+
"000,000,255,255,000\n";
69+
static const MicroBitImage MOON(moon);
70+
71+
// MakeCode melodies in the format NOTE[octave][:duration]
72+
static const int DEFAULT_TEMPO_BPM = 120;
73+
static const int MS_PER_BPM = (60000 / DEFAULT_TEMPO_BPM) / 4;
74+
static const int NOTE_LEN = 6;
75+
#define TUNE_LEN(tune) (sizeof(tune) / sizeof(tune[0]))
76+
77+
static const char MELODY_POWER_UP[][NOTE_LEN] = {"G4:1", "C5", "E", "G:2", "E:1", "G:3"};
78+
static const char MELODY_POWER_DOWN[][NOTE_LEN] = {"G5:1", "D#", "C", "G4:2", "B5:1", "C:3"};
79+
80+
81+
/**
82+
* Displays a vertical bar graph based on the `value` and `high` arguments.
83+
*
84+
* Function adapted from the MakeCode project, Copyright Microsoft Corporation
85+
* MIT Licensed (MIT):
86+
* https://github.com/microsoft/pxt-microbit/blob/v5.1.25/libs/core/led.ts#L42-L83
87+
* https://github.com/microsoft/pxt-microbit/blob/v5.1.25/LICENSE.txt
88+
*
89+
* @param value - Current value to plot.
90+
* @param high - Maximum value.
91+
*/
92+
static void plotBarGraph(uint32_t value, int high) {
93+
float v = (float)value / (float)high;
94+
float dv = 1.0 / 16.0;
95+
float k = 0;
96+
for (int y = 4; y >= 0; --y) {
97+
for (int x = 0; x < 3; ++x) {
98+
if (k > v) {
99+
uBit.display.image.setPixelValue(2 - x, y, 0);
100+
uBit.display.image.setPixelValue(2 + x, y, 0);
101+
} else {
102+
uBit.display.image.setPixelValue(2 - x, y, 255);
103+
uBit.display.image.setPixelValue(2 + x, y, 255);
104+
}
105+
k += dv;
106+
}
107+
}
108+
}
109+
110+
static void playMelody(const char melody[][NOTE_LEN], size_t len) {
111+
DMESG("Tune len: %d", len);
112+
113+
// Starting from default values, optional octave & duration values are remembered
114+
int octave = 4;
115+
int durationMs = 4 * MS_PER_BPM;
116+
117+
for (size_t i = 0; i < len; i++) {
118+
const char *note = melody[i];
119+
const char *note_char = &melody[i][0];
120+
int distanceFromA = 0;
121+
int frequency = 0;
122+
bool rest = false;
123+
124+
// First process the note, as its distance from A
125+
switch (*note_char) {
126+
case 'A': distanceFromA = 0; break;
127+
case 'B': distanceFromA = 2; break;
128+
case 'C': distanceFromA = -9; break;
129+
case 'D': distanceFromA = -7; break;
130+
case 'E': distanceFromA = -5; break;
131+
case 'F': distanceFromA = -4; break;
132+
case 'G': distanceFromA = -2; break;
133+
case 'R': rest = true; break;
134+
default: target_panic(123); break;
135+
}
136+
137+
// Then process the optional #/b modifiers and/or scale
138+
note_char++;
139+
while (*note_char != ':' && *note_char != '\0') {
140+
if (*note_char == '#') {
141+
distanceFromA++;
142+
} else if (*note_char == 'b') {
143+
distanceFromA--;
144+
} else if ((*note_char >= '0') && (*note_char <= '9')) {
145+
octave = (*note_char - '0');
146+
} else {
147+
target_panic(124);
148+
}
149+
note_char++;
150+
}
151+
152+
// If an optional duration is present, calculate the delay in ms
153+
// Note, only a single digit implemented right now
154+
if (*note_char == ':') {
155+
note_char++;
156+
if ((*note_char < '0') || (*note_char > '9')) target_panic(125);
157+
durationMs = atoi((const char*)note_char) * MS_PER_BPM;
158+
}
159+
160+
// Calculate note frequency, or keep it as zero for a rest
161+
if (!rest) {
162+
float distanceFromA4 = (octave - 4) * 12 + distanceFromA;
163+
frequency = 440.0 * pow(2, distanceFromA4 / 12.0);
164+
}
165+
166+
// Play the tone/rest for the calculated duration
167+
DMESG("%s -> f:%u, ms:%d", note, frequency, durationMs);
168+
if (frequency) {
169+
uBit.audio.virtualOutputPin.setAnalogPeriodUs(1000000 / frequency);
170+
uBit.audio.virtualOutputPin.setAnalogValue(127);
171+
}
172+
uBit.sleep(durationMs);
173+
uBit.audio.virtualOutputPin.setAnalogValue(0);
174+
175+
// Short break between notes
176+
uBit.sleep(10);
177+
}
178+
}
179+
180+
static void onButtonA(MicroBitEvent) {
181+
DMESG("Button A");
182+
uBit.audio.soundExpressions.playAsync("spring");
183+
uBit.display.print(HAPPY);
184+
}
185+
186+
static void onButtonB(MicroBitEvent) {
187+
DMESG("Button B");
188+
uBit.audio.soundExpressions.playAsync("sad");
189+
uBit.display.print(SAD);
190+
}
191+
192+
static void onButtonAB(MicroBitEvent) {
193+
DMESG("Button A+B");
194+
195+
int lightLevel = uBit.display.readLightLevel();
196+
DMESG("Light level: %d", lightLevel);
197+
// Reset display mode to disable light level reading & avoid LED flicker
198+
uBit.display.setDisplayMode(DisplayMode::DISPLAY_MODE_BLACK_AND_WHITE);
199+
200+
if (lightLevel > 50) {
201+
uBit.display.print(SUN);
202+
playMelody(MELODY_POWER_UP, TUNE_LEN(MELODY_POWER_UP));
203+
} else {
204+
uBit.display.print(MOON);
205+
playMelody(MELODY_POWER_DOWN, TUNE_LEN(MELODY_POWER_DOWN));
206+
}
207+
}
208+
209+
static void onButtonLogo(MicroBitEvent) {
210+
DMESG("Button Logo");
211+
212+
int sampleRate = 11000;
213+
static SplitterChannel *splitterChannel = uBit.audio.splitter->createChannel();
214+
splitterChannel->requestSampleRate( sampleRate );
215+
216+
// Uncomment these two lines and comment out the *recording declaration after them to insert a low-pass-filter.
217+
// static LowPassFilter *lowPassFilter = new LowPassFilter(*splitterChannel, 0.812313f, false);
218+
// static StreamRecording *recording = new StreamRecording(*lowPassFilter);
219+
static StreamRecording *recording = new StreamRecording(*splitterChannel);
220+
221+
static MixerChannel *channel = uBit.audio.mixer.addChannel(*recording, sampleRate);
222+
223+
// uBit.audio.processor->setGain(0.08f); // Default gain
224+
// uBit.audio.processor->setGain(0.16f); // Double
225+
226+
MicroBitAudio::requestActivation();
227+
channel->setVolume(75.0);
228+
uBit.audio.mixer.setVolume(1023);
229+
230+
uBit.display.clear();
231+
uBit.audio.levelSPL->setUnit(LEVEL_DETECTOR_SPL_8BIT);
232+
233+
recording->recordAsync();
234+
while (uBit.logo.isPressed() && recording->isRecording()) {
235+
int audioLevel = (int)uBit.audio.levelSPL->getValue();
236+
DMESG("Audio level: %d", audioLevel);
237+
plotBarGraph(audioLevel, 255);
238+
uBit.sleep(5);
239+
}
240+
// At this point either the logo has been released or the recording is done
241+
recording->stop();
242+
// Note: The CODAL_STREAM_IDLE_TIMEOUT_MS config has been set in the
243+
// codal.json file to reduce the time it takes for the microphone LED
244+
// to turn off after the recording is done.
245+
// This area is still being tweaked in CODAL and the codal.json config
246+
// should be removed in the future to use the CODAL default.
247+
uBit.display.clear();
248+
249+
// If the recording is done but the logo is still pressed we want to
250+
// hold back the playback until the logo has been released
251+
while (uBit.logo.isPressed());
252+
253+
recording->play();
254+
while (recording->isPlaying()) {
255+
uBit.sleep(20);
256+
}
257+
recording->erase();
258+
}
259+
260+
static void onShake(MicroBitEvent) {
261+
DMESG("Shake");
262+
uBit.display.print(SILENT);
263+
uBit.sleep(400);
264+
265+
// Sound Effect:
266+
// music.createSoundEffect(WaveShape.Sine, 3041, 3923, 59, 255, 500, SoundExpressionEffect.Warble, InterpolationCurve.Linear)
267+
// audio.SoundEffect(
268+
// freq_start=3041, freq_end=3923,
269+
// vol_start=59, vol_end=255,
270+
// duration=500,
271+
// waveform=audio.SoundEffect.WAVEFORM_SINE,
272+
// fx=audio.SoundEffect.FX_WARBLE,
273+
// shape=audio.SoundEffect.SHAPE_LINEAR,
274+
// )
275+
uBit.audio.soundExpressions.playAsync("002373041050001000392300001023010802050005000000000000000000000000000000");
276+
277+
uBit.display.print(SINGING);
278+
uBit.sleep(400);
279+
uBit.display.print(SILENT);
280+
}
281+
282+
static void onScreenDown(MicroBitEvent) {
283+
DMESG("Display Down");
284+
285+
// Sound Effect:
286+
// music.createSoundEffect(WaveShape.Sine, 849, 1, 255, 0, 1000, SoundExpressionEffect.None, InterpolationCurve.Linear)
287+
// audio.SoundEffect(
288+
// freq_start=849, freq_end=1,
289+
// vol_start=255, vol_end=0,
290+
// duration=1000,
291+
// waveform=audio.SoundEffect.WAVEFORM_SINE,
292+
// fx=audio.SoundEffect.FX_NONE,
293+
// shape=audio.SoundEffect.SHAPE_LINEAR,
294+
// )
295+
uBit.audio.soundExpressions.play("010230849100001000000100000000012800000100240000000000000000000000000000");
296+
297+
uBit.display.print(ASLEEP);
298+
}
299+
300+
static void onStart() {
301+
uBit.audio.soundExpressions.playAsync("hello");
302+
uBit.display.print(HEART);
303+
}
304+
305+
void out_of_box_experience() {
306+
uBit.messageBus.listen(MICROBIT_ID_BUTTON_A, MICROBIT_BUTTON_EVT_CLICK, onButtonA);
307+
uBit.messageBus.listen(MICROBIT_ID_BUTTON_B, MICROBIT_BUTTON_EVT_CLICK, onButtonB);
308+
uBit.messageBus.listen(MICROBIT_ID_BUTTON_AB, MICROBIT_BUTTON_EVT_CLICK, onButtonAB);
309+
uBit.messageBus.listen(MICROBIT_ID_LOGO, MICROBIT_BUTTON_EVT_DOWN, onButtonLogo);
310+
uBit.messageBus.listen(MICROBIT_ID_GESTURE, MICROBIT_ACCELEROMETER_EVT_SHAKE, onShake);
311+
uBit.messageBus.listen(MICROBIT_ID_GESTURE, MICROBIT_ACCELEROMETER_EVT_FACE_DOWN, onScreenDown);
312+
313+
onStart();
314+
315+
//uBit.audio.rawSplitter->status |= DEVICE_COMPONENT_STATUS_SYSTEM_TICK;
316+
//uBit.audio.splitter->status |= DEVICE_COMPONENT_STATUS_SYSTEM_TICK;
317+
318+
while (true) {
319+
uBit.sleep(1000);
320+
}
321+
}

source/samples/Tests.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ void mixer_test2();
9292
void speaker_pin_test();
9393
void say_hello();
9494
void stream_mixer_to_serial();
95+
void out_of_box_experience_v2();
9596
void out_of_box_experience();
9697
void level_meter();
9798
void init_clap_detect();

0 commit comments

Comments
 (0)