|
| 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 | +} |
0 commit comments