| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | |||
| 2 | /* | ||
| 3 | * Pixel Overlay Effects for Falcon Player (FPP) ported from WLED | ||
| 4 | * | ||
| 5 | * This file is part of the Falcon Player (FPP) and is Copyright (C) | ||
| 6 | * 2013-2022 by the Falcon Player Developers. | ||
| 7 | * | ||
| 8 | * The Falcon Player (FPP) is free software, and is covered under | ||
| 9 | * multiple Open Source licenses. Please see the included 'LICENSES' | ||
| 10 | * file for descriptions of what files are covered by each license. | ||
| 11 | * | ||
| 12 | * This source file is covered under the LGPL v2.1 as described in the | ||
| 13 | * included LICENSE.LGPL file. | ||
| 14 | */ | ||
| 15 | |||
| 16 | #include "fpp-pch.h" | ||
| 17 | #include "log.h" | ||
| 18 | |||
| 19 | #include "../../settings.h" | ||
| 20 | #include "../PixelOverlayModel.h" | ||
| 21 | |||
| 22 | #include <cmath> | ||
| 23 | #include <mutex> | ||
| 24 | #include <math.h> | ||
| 25 | #include <time.h> | ||
| 26 | |||
| 27 | #ifdef HAS_GSTREAMER | ||
| 28 | #include <gst/gst.h> | ||
| 29 | #include <gst/app/gstappsink.h> | ||
| 30 | #endif | ||
| 31 | |||
| 32 | #include "wled.h" | ||
| 33 | |||
| 34 | #include "kiss_fftr.h" | ||
| 35 | #include "../../Warnings.h" | ||
| 36 | #include "../../mediaoutput/GStreamerOut.h" | ||
| 37 | #include "../../WLEDAudioSync.h" | ||
| 38 | |||
| 39 | uint8_t blendingStyle = 0; // effect blending/transitionig style | ||
| 40 | |||
| 41 | uint16_t rand16seed = 0; | ||
| 42 | time_t localTime = time(nullptr); | ||
| 43 | uint8_t randomPaletteChangeTime = 0; | ||
| 44 | bool stateChanged = false; | ||
| 45 | byte lastRandomIndex = 0; | ||
| 46 | char* ledmapNames[WLED_MAX_LEDMAPS] = { nullptr }; | ||
| 47 | uint16_t ledMaps = 0xFFFF; | ||
| 48 | std::vector<BusConfig> busConfigs; | ||
| 49 | uint8_t errorFlag = 0; | ||
| 50 | |||
| 51 | uint8_t briS = (128); // default brightness | ||
| 52 | uint8_t bri = (briS); // global brightness (set) | ||
| 53 | uint8_t briOld = (0); // global brightness while in transition loop (previous iteration) | ||
| 54 | uint8_t briT = (0); // global brightness during transition | ||
| 55 | uint8_t briLast = (128); // brightness before turned off. Used for toggle function | ||
| 56 | uint8_t whiteLast = (128); // white channel before turned off. Used for toggle function in ir.cpp | ||
| 57 | |||
| 58 | bool useHarmonicRandomPalette = true; | ||
| 59 | bool useGlobalLedBuffer = false; | ||
| 60 | uint8_t realtimeMode = 0; | ||
| 61 | bool realtimeRespectLedMaps = true; | ||
| 62 | |||
| 63 | char settingsPIN[5] = ""; // PIN for settings pages | ||
| 64 | bool correctPIN = 0; | ||
| 65 | unsigned long lastEditTime = 0; | ||
| 66 | |||
| 67 | FakeFL FastLED; | ||
| 68 | Bus BusManager::bus; | ||
| 69 | WLEDFileSystem WLED_FS; | ||
| 70 | |||
| 71 | 142 | int hour(time_t t) { | |
| 72 | struct tm tm; | ||
| 73 | 142 | localtime_r(&t, &tm); | |
| 74 | 142 | return tm.tm_hour; | |
| 75 | } | ||
| 76 | 18 | int minute(time_t t) { | |
| 77 | struct tm tm; | ||
| 78 | 18 | localtime_r(&t, &tm); | |
| 79 | 18 | return tm.tm_min; | |
| 80 | } | ||
| 81 | 142 | int second(time_t t) { | |
| 82 | struct tm tm; | ||
| 83 | 142 | localtime_r(&t, &tm); | |
| 84 | 142 | return tm.tm_sec; | |
| 85 | } | ||
| 86 | 18 | int day(time_t t) { | |
| 87 | struct tm tm; | ||
| 88 | 18 | localtime_r(&t, &tm); | |
| 89 | 18 | return tm.tm_mday; | |
| 90 | } | ||
| 91 | // TimeLib semantics, which the WLED effects were written against: month() | ||
| 92 | // is 1-12, year() the full year, weekday() 1-7 with Sunday = 1. These | ||
| 93 | // returned tm_mon and tm_year as-is, so January printed "Err" and #YYYY "126". | ||
| 94 | 18 | int month(time_t t) { | |
| 95 | struct tm tm; | ||
| 96 | 18 | localtime_r(&t, &tm); | |
| 97 | 18 | return tm.tm_mon + 1; | |
| 98 | } | ||
| 99 | 18 | int year(time_t t) { | |
| 100 | struct tm tm; | ||
| 101 | 18 | localtime_r(&t, &tm); | |
| 102 | 18 | return tm.tm_year + 1900; | |
| 103 | } | ||
| 104 | 62 | int weekday(time_t t) { | |
| 105 | struct tm tm; | ||
| 106 | 62 | localtime_r(&t, &tm); | |
| 107 | 62 | return tm.tm_wday + 1; | |
| 108 | } | ||
| 109 | static const char* monthShortNames_P[] = { "Err", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; | ||
| 110 | static const char* monthNames_P[] = { "Err", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; | ||
| 111 | // Index 0 is "Err"; anything outside the table maps there too rather than | ||
| 112 | // handing sprintf a pointer from past the end of the array. | ||
| 113 | ✗ | const char* monthStr(uint8_t m) { | |
| 114 | ✗ | return monthNames_P[m <= 12 ? m : 0]; | |
| 115 | } | ||
| 116 | 18 | const char* monthShortStr(uint8_t m) { | |
| 117 |
1/2✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
|
18 | return monthShortNames_P[m <= 12 ? m : 0]; |
| 118 | } | ||
| 119 | |||
| 120 | static const char* datStrShort_P[] = { "Err", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; | ||
| 121 | static const char* datStr_P[] = { "Err", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; | ||
| 122 | ✗ | const char* dayStr(uint8_t day) { | |
| 123 | ✗ | return datStr_P[day <= 7 ? day : 0]; | |
| 124 | } | ||
| 125 | 62 | const char* dayShortStr(uint8_t day) { | |
| 126 |
1/2✓ Branch 0 taken 62 times.
✗ Branch 1 not taken.
|
62 | return datStrShort_P[day <= 7 ? day : 0]; |
| 127 | } | ||
| 128 | |||
| 129 | ✗ | uint16_t XY(uint8_t x, uint8_t y) { | |
| 130 | ✗ | return x; | |
| 131 | } | ||
| 132 | 128508 | uint32_t get_millisecond_timer() { | |
| 133 | 128508 | return GetMonotonicTimeMS(); | |
| 134 | } | ||
| 135 | |||
| 136 | // RMS average | ||
| 137 | ✗ | static float fftAddAvgRMS(int from, int to, float* samples) { | |
| 138 | ✗ | double result = 0.0; | |
| 139 | ✗ | for (int i = from; i <= to; i++) { | |
| 140 | ✗ | result += samples[i] * samples[i]; | |
| 141 | } | ||
| 142 | ✗ | return sqrtf(result / float(to - from + 1)); | |
| 143 | } | ||
| 144 | |||
| 145 | ✗ | static float fftAddAvg(int from, int to, float* samples) { | |
| 146 | ✗ | if (from == to) | |
| 147 | ✗ | return samples[from]; // small optimization | |
| 148 | ✗ | return fftAddAvgRMS(from, to, samples); // use SMS | |
| 149 | } | ||
| 150 | |||
| 151 | /* | ||
| 152 | static void GenerateSinWave(std::array<float, NUM_SAMPLES> &samples, float sampleRate, float frequency = 440) | ||
| 153 | { | ||
| 154 | float sampleDurationSeconds = 1.0 / sampleRate; | ||
| 155 | for (int i = 0; i < NUM_SAMPLES; ++i) { | ||
| 156 | double sampleTime = (float)(i) * sampleDurationSeconds; | ||
| 157 | samples[i] = std::sin(2.0 * 3.14159 * frequency * sampleTime); | ||
| 158 | } | ||
| 159 | } | ||
| 160 | void HammingWindow(std::array<float, NUM_SAMPLES> &samples) | ||
| 161 | { | ||
| 162 | static const double a0 = 25.0 / 46.0; | ||
| 163 | static const double a1 = 1 - a0; | ||
| 164 | for (int i = 0; i < NUM_SAMPLES; ++i) | ||
| 165 | samples[i] *= a0 - a1 * cos((2.0 * 3.14159 * i) / NUM_SAMPLES); | ||
| 166 | } | ||
| 167 | */ | ||
| 168 | |||
| 169 | class WLEDAudioReactiveSoundSource { | ||
| 170 | public: | ||
| 171 | 2346 | WLEDAudioReactiveSoundSource() { | |
| 172 | 2346 | } | |
| 173 | 458 | ~WLEDAudioReactiveSoundSource() { | |
| 174 | #ifdef HAS_GSTREAMER | ||
| 175 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 458 times.
|
458 | if (gstPipeline) { |
| 176 | ✗ | gst_element_set_state(gstPipeline, GST_STATE_NULL); | |
| 177 | ✗ | gst_object_unref(gstPipeline); | |
| 178 | ✗ | gstPipeline = nullptr; | |
| 179 | } | ||
| 180 | #endif | ||
| 181 | 458 | } | |
| 182 | static void InputAudioCallback(WLEDAudioReactiveSoundSource* p, uint8_t* stream, int len) { | ||
| 183 | int16_t* data = (int16_t*)stream; | ||
| 184 | int numSamples = len / sizeof(int16_t); | ||
| 185 | int start = 0; | ||
| 186 | if (numSamples > NUM_SAMPLES) { | ||
| 187 | numSamples = NUM_SAMPLES; | ||
| 188 | start = numSamples - NUM_SAMPLES; | ||
| 189 | } | ||
| 190 | p->inputSamples.fill(0); | ||
| 191 | for (int i = 0; i < numSamples; i++) { | ||
| 192 | p->inputSamples[i] = data[start + i] / 32768.0f; | ||
| 193 | } | ||
| 194 | } | ||
| 195 | |||
| 196 | #ifdef HAS_GSTREAMER | ||
| 197 | // Open a PipeWire audio source via GStreamer pipewiresrc. | ||
| 198 | // dev is the PipeWire node.name (e.g. "alsa_input.usb-0d8c_USB_Sound_Device-00.analog-stereo") | ||
| 199 | 1 | void openGStreamerCapture(const std::string& dev) { | |
| 200 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | GStreamerOutput::EnsureGStreamerInit(); |
| 201 | |||
| 202 | // Build pipeline: pipewiresrc target-object=<node> ! audioconvert ! audioresample ! | ||
| 203 | // audio/x-raw,format=S16LE,channels=1,rate=44100 ! appsink name=wledsink | ||
| 204 | // The node name is set as a property after parsing, never pasted into | ||
| 205 | // the description: it comes from the WLEDAudioInput setting (writable | ||
| 206 | // over the network), and pasted in, "x ! filesrc location=... ! ..." | ||
| 207 | // added elements that read or wrote any file as root. | ||
| 208 | std::string pipelineStr = | ||
| 209 | "pipewiresrc name=wledsrc ! audioconvert ! audioresample ! " | ||
| 210 | "audio/x-raw,format=S16LE,channels=1,rate=44100 ! " | ||
| 211 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | "appsink name=wledsink max-buffers=2 drop=true"; |
| 212 | |||
| 213 | 1 | GError* error = nullptr; | |
| 214 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | gstPipeline = gst_parse_launch(pipelineStr.c_str(), &error); |
| 215 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (error) { |
| 216 | ✗ | WarningHolder::AddWarning(52, "WLED Sound Reactive - GStreamer pipeline error: " + std::string(error->message)); | |
| 217 | ✗ | LogErr(VB_MEDIAOUT, "WLED GStreamer capture pipeline error: %s\n", error->message); | |
| 218 | ✗ | g_error_free(error); | |
| 219 | ✗ | if (gstPipeline) { | |
| 220 | ✗ | gst_object_unref(gstPipeline); | |
| 221 | ✗ | gstPipeline = nullptr; | |
| 222 | } | ||
| 223 | ✗ | return; | |
| 224 | } | ||
| 225 |
4/8✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
|
1 | if (GstElement* src = gst_bin_get_by_name(GST_BIN(gstPipeline), "wledsrc")) { |
| 226 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | g_object_set(src, "target-object", dev.c_str(), nullptr); |
| 227 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | gst_object_unref(src); |
| 228 | } | ||
| 229 | |||
| 230 |
3/6✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
|
1 | GstElement* appsink = gst_bin_get_by_name(GST_BIN(gstPipeline), "wledsink"); |
| 231 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (!appsink) { |
| 232 | ✗ | WarningHolder::AddWarning(52, "WLED Sound Reactive - Could not find appsink in pipeline"); | |
| 233 | ✗ | gst_object_unref(gstPipeline); | |
| 234 | ✗ | gstPipeline = nullptr; | |
| 235 | ✗ | return; | |
| 236 | } | ||
| 237 |
3/6✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
|
1 | gst_app_sink_set_emit_signals(GST_APP_SINK(appsink), FALSE); |
| 238 | 1 | gstAppsink = appsink; | |
| 239 | |||
| 240 | // On the system clock, not the GstPipeWireClock pipewiresrc offers, | ||
| 241 | // as the RTSP audio bridge and Opus sender do: that clock reads the | ||
| 242 | // stream's driver position and stops advancing when the stream's | ||
| 243 | // time reports no rate, and the appsink waits on it before taking | ||
| 244 | // each buffer, so audio sync sent nothing more and reactive effects | ||
| 245 | // went still. | ||
| 246 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | GstClock* sysClock = gst_system_clock_obtain(); |
| 247 |
3/6✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
|
1 | gst_pipeline_use_clock(GST_PIPELINE(gstPipeline), sysClock); |
| 248 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | gst_object_unref(sysClock); |
| 249 | |||
| 250 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | GstStateChangeReturn ret = gst_element_set_state(gstPipeline, GST_STATE_PLAYING); |
| 251 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (ret == GST_STATE_CHANGE_FAILURE) { |
| 252 | ✗ | WarningHolder::AddWarning(52, "WLED Sound Reactive - Could not start GStreamer capture pipeline for: " + dev); | |
| 253 | ✗ | LogErr(VB_MEDIAOUT, "WLED GStreamer capture: failed to start pipeline for %s\n", dev.c_str()); | |
| 254 | ✗ | gst_object_unref(appsink); | |
| 255 | ✗ | gstAppsink = nullptr; | |
| 256 | ✗ | gst_object_unref(gstPipeline); | |
| 257 | ✗ | gstPipeline = nullptr; | |
| 258 | ✗ | return; | |
| 259 | } | ||
| 260 | |||
| 261 | 1 | inputSampleRate = 44100; | |
| 262 | 1 | gstCaptureActive = true; | |
| 263 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | LogInfo(VB_MEDIAOUT, "WLED GStreamer capture: opened PipeWire source '%s'\n", dev.c_str()); |
| 264 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | } |
| 265 | |||
| 266 | 370 | bool pullGStreamerSamples() { | |
| 267 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 370 times.
|
370 | if (!gstAppsink) |
| 268 | ✗ | return false; | |
| 269 | |||
| 270 |
3/6✓ Branch 0 taken 370 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 370 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 370 times.
✗ Branch 5 not taken.
|
370 | GstSample* sample = gst_app_sink_try_pull_sample(GST_APP_SINK(gstAppsink), 0); |
| 271 |
2/2✓ Branch 0 taken 87 times.
✓ Branch 1 taken 283 times.
|
370 | if (!sample) |
| 272 | 87 | return false; | |
| 273 | |||
| 274 |
1/2✓ Branch 0 taken 283 times.
✗ Branch 1 not taken.
|
283 | GstBuffer* buffer = gst_sample_get_buffer(sample); |
| 275 | GstMapInfo map; | ||
| 276 |
2/4✓ Branch 0 taken 283 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 283 times.
✗ Branch 3 not taken.
|
283 | if (gst_buffer_map(buffer, &map, GST_MAP_READ)) { |
| 277 | 283 | int16_t* data = (int16_t*)map.data; | |
| 278 | 283 | int numSamples = map.size / sizeof(int16_t); | |
| 279 | 283 | int start = 0; | |
| 280 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 283 times.
|
283 | if (numSamples > NUM_SAMPLES) { |
| 281 | ✗ | start = numSamples - NUM_SAMPLES; | |
| 282 | ✗ | numSamples = NUM_SAMPLES; | |
| 283 | } | ||
| 284 |
1/2✓ Branch 0 taken 283 times.
✗ Branch 1 not taken.
|
283 | inputSamples.fill(0); |
| 285 | // Normalize S16 to +/-1.0. computeAudioFrame() assumes that range | ||
| 286 | // -- the "Playing Media" tap feeds it F32LE samples already scaled | ||
| 287 | // that way. Handing it raw int16 puts every value 32768x too high, | ||
| 288 | // which pins volumeSmth and all 16 FFT bins at 255 and stops | ||
| 289 | // samplePeak ever firing, so reactive effects sit flat. | ||
| 290 |
2/2✓ Branch 0 taken 266247 times.
✓ Branch 1 taken 283 times.
|
266530 | for (int i = 0; i < numSamples; i++) { |
| 291 | 266247 | inputSamples[i] = data[start + i] / 32768.0f; | |
| 292 | } | ||
| 293 |
1/2✓ Branch 0 taken 283 times.
✗ Branch 1 not taken.
|
283 | gst_buffer_unmap(buffer, &map); |
| 294 | } | ||
| 295 |
1/2✓ Branch 0 taken 283 times.
✗ Branch 1 not taken.
|
283 | gst_sample_unref(sample); |
| 296 | 283 | return true; | |
| 297 | } | ||
| 298 | |||
| 299 | 1 | void openGStreamerAlsaCapture(const std::string& dev) { | |
| 300 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | GStreamerOutput::EnsureGStreamerInit(); |
| 301 | |||
| 302 | // Build pipeline: alsasrc device=<dev> ! audioconvert ! audioresample ! | ||
| 303 | // audio/x-raw,format=S16LE,channels=1,rate=44100 ! appsink | ||
| 304 | // The device is set as a property, not pasted into the description; | ||
| 305 | // see openGStreamerCapture(). | ||
| 306 | std::string pipelineStr = | ||
| 307 | "alsasrc name=wledsrc ! audioconvert ! audioresample ! " | ||
| 308 | "audio/x-raw,format=S16LE,channels=1,rate=44100 ! " | ||
| 309 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | "appsink name=wledsink max-buffers=2 drop=true"; |
| 310 | |||
| 311 | 1 | GError* error = nullptr; | |
| 312 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | gstPipeline = gst_parse_launch(pipelineStr.c_str(), &error); |
| 313 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (error) { |
| 314 | ✗ | WarningHolder::AddWarning(52, "WLED Sound Reactive - Could not open Audio Input Device: " + dev + " Error: " + std::string(error->message)); | |
| 315 | ✗ | LogErr(VB_MEDIAOUT, "WLED GStreamer ALSA capture pipeline error: %s\n", error->message); | |
| 316 | ✗ | g_error_free(error); | |
| 317 | ✗ | if (gstPipeline) { | |
| 318 | ✗ | gst_object_unref(gstPipeline); | |
| 319 | ✗ | gstPipeline = nullptr; | |
| 320 | } | ||
| 321 | ✗ | return; | |
| 322 | } | ||
| 323 |
4/8✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
|
1 | if (GstElement* src = gst_bin_get_by_name(GST_BIN(gstPipeline), "wledsrc")) { |
| 324 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | g_object_set(src, "device", dev.c_str(), nullptr); |
| 325 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | gst_object_unref(src); |
| 326 | } | ||
| 327 | |||
| 328 |
3/6✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
|
1 | GstElement* appsink = gst_bin_get_by_name(GST_BIN(gstPipeline), "wledsink"); |
| 329 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (!appsink) { |
| 330 | ✗ | WarningHolder::AddWarning(52, "WLED Sound Reactive - Could not find appsink in pipeline"); | |
| 331 | ✗ | gst_object_unref(gstPipeline); | |
| 332 | ✗ | gstPipeline = nullptr; | |
| 333 | ✗ | return; | |
| 334 | } | ||
| 335 |
3/6✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
|
1 | gst_app_sink_set_emit_signals(GST_APP_SINK(appsink), FALSE); |
| 336 | 1 | gstAppsink = appsink; | |
| 337 | |||
| 338 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | GstStateChangeReturn ret = gst_element_set_state(gstPipeline, GST_STATE_PLAYING); |
| 339 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (ret == GST_STATE_CHANGE_FAILURE) { |
| 340 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | WarningHolder::AddWarning(52, "WLED Sound Reactive - Could not start ALSA capture for: " + dev); |
| 341 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | LogErr(VB_MEDIAOUT, "WLED GStreamer ALSA capture: failed to start pipeline for %s\n", dev.c_str()); |
| 342 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | gst_object_unref(appsink); |
| 343 | 1 | gstAppsink = nullptr; | |
| 344 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | gst_object_unref(gstPipeline); |
| 345 | 1 | gstPipeline = nullptr; | |
| 346 | 1 | return; | |
| 347 | } | ||
| 348 | |||
| 349 | ✗ | inputSampleRate = 44100; | |
| 350 | ✗ | gstCaptureActive = true; | |
| 351 | ✗ | LogInfo(VB_MEDIAOUT, "WLED GStreamer ALSA capture: opened device '%s'\n", dev.c_str()); | |
| 352 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | } |
| 353 | #endif | ||
| 354 | |||
| 355 | 2 | void openAudioDevice(const std::string& dev) { | |
| 356 | #ifdef HAS_GSTREAMER | ||
| 357 | // In PipeWire mode, dev is a PipeWire node.name — use GStreamer pipewiresrc | ||
| 358 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
2 | if (isPipeWireBackend()) { |
| 359 | 1 | openGStreamerCapture(dev); | |
| 360 | 1 | return; | |
| 361 | } | ||
| 362 | // ALSA mode — use GStreamer alsasrc | ||
| 363 | 1 | openGStreamerAlsaCapture(dev); | |
| 364 | #else | ||
| 365 | WarningHolder::AddWarning(52, "WLED Sound Reactive - Audio capture requires GStreamer"); | ||
| 366 | #endif | ||
| 367 | } | ||
| 368 | |||
| 369 | 3825 | bool getAudioSamples(std::array<float, NUM_SAMPLES>& samples, int& sampleRate) { | |
| 370 | // Called from the overlay effect thread (reactive effects) and the | ||
| 371 | // WLEDAudioSync thread: both opened the capture on first use (one | ||
| 372 | // pipeline leaked) and filled inputSamples at once. Every pull is | ||
| 373 | // non-blocking, so holding this over the call costs little. | ||
| 374 |
1/2✓ Branch 0 taken 3825 times.
✗ Branch 1 not taken.
|
3825 | std::lock_guard<std::mutex> lk(sourceLock); |
| 375 |
2/2✓ Branch 0 taken 15 times.
✓ Branch 1 taken 3810 times.
|
3825 | if (sourceType == -1) { |
| 376 |
1/2✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
|
15 | std::string source = getSetting("WLEDAudioInput", "-- Playing Media --"); |
| 377 |
3/4✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✓ Branch 3 taken 2 times.
|
15 | if (source == "-- Playing Media --") { |
| 378 | 13 | sourceType = 0; | |
| 379 | } else { | ||
| 380 | 2 | sourceType = 1; | |
| 381 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | openAudioDevice(source); |
| 382 | } | ||
| 383 | 15 | } | |
| 384 | 3825 | bool retValue = false; | |
| 385 |
2/2✓ Branch 0 taken 3351 times.
✓ Branch 1 taken 474 times.
|
3825 | if (sourceType == 0) { |
| 386 | // Playing Media — use GStreamer | ||
| 387 | #ifdef HAS_GSTREAMER | ||
| 388 |
1/2✓ Branch 0 taken 3351 times.
✗ Branch 1 not taken.
|
3351 | retValue = GStreamerOutput::GetAudioSamples(&samples[0], NUM_SAMPLES, sampleRate); |
| 389 | #endif | ||
| 390 |
1/2✓ Branch 0 taken 474 times.
✗ Branch 1 not taken.
|
474 | } else if (sourceType == 1) { |
| 391 | // Audio Input — GStreamer capture (PipeWire or ALSA) | ||
| 392 | #ifdef HAS_GSTREAMER | ||
| 393 |
2/2✓ Branch 0 taken 370 times.
✓ Branch 1 taken 104 times.
|
474 | if (gstCaptureActive) { |
| 394 |
1/2✓ Branch 0 taken 370 times.
✗ Branch 1 not taken.
|
370 | retValue = pullGStreamerSamples(); |
| 395 |
2/2✓ Branch 0 taken 283 times.
✓ Branch 1 taken 87 times.
|
370 | if (retValue) { |
| 396 | 283 | samples = inputSamples; | |
| 397 | 283 | sampleRate = inputSampleRate; | |
| 398 | } | ||
| 399 | 370 | return retValue; | |
| 400 | } | ||
| 401 | #endif | ||
| 402 | } | ||
| 403 | 3455 | return retValue; | |
| 404 | 3825 | } | |
| 405 | |||
| 406 | std::mutex sourceLock; | ||
| 407 | int sourceType = -1; | ||
| 408 | std::array<float, NUM_SAMPLES> inputSamples; | ||
| 409 | int inputSampleRate = 44100; | ||
| 410 | #ifdef HAS_GSTREAMER | ||
| 411 | GstElement* gstPipeline = nullptr; | ||
| 412 | GstElement* gstAppsink = nullptr; | ||
| 413 | bool gstCaptureActive = false; | ||
| 414 | #endif | ||
| 415 | |||
| 416 | } WLED_SOUND_SOURCE; | ||
| 417 | |||
| 418 | // Free-function entry points (declared in wled.h) so external callers | ||
| 419 | // — notably WLEDAudioSync — can pull a sample buffer and run the same | ||
| 420 | // FFT pipeline the WLED reactive effects use, without holding a | ||
| 421 | // WS2812FXExt instance. | ||
| 422 | 2882 | bool fetchAudioSamples(std::array<float, NUM_SAMPLES>& samples, int& sampleRate) { | |
| 423 | 2882 | return WLED_SOUND_SOURCE.getAudioSamples(samples, sampleRate); | |
| 424 | } | ||
| 425 | |||
| 426 | 2470 | void computeAudioFrame(const std::array<float, NUM_SAMPLES>& samples, | |
| 427 | int sampleRate, | ||
| 428 | AudioFrame& out) { | ||
| 429 | // The FFT config (with its scratch buffer) and the volume smoothing below | ||
| 430 | // are statics, used by every strip's service() and by the WLED Audio | ||
| 431 | // Sync sender on its own thread; concurrent calls corrupted each other's | ||
| 432 | // spectrum. Recursive: service() already holds it. | ||
| 433 |
2/4✓ Branch 0 taken 2470 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2470 times.
✗ Branch 3 not taken.
|
2470 | std::lock_guard<std::recursive_mutex> l(WS2812FXExt::lock()); |
| 434 |
4/8✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2466 times.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
2470 | static kiss_fftr_cfg cfg = kiss_fftr_alloc(NUM_SAMPLES, false, 0, 0); |
| 435 | kiss_fft_cpx fft_out[NUM_SAMPLES]; | ||
| 436 |
1/2✓ Branch 0 taken 2470 times.
✗ Branch 1 not taken.
|
2470 | kiss_fftr(cfg, (kiss_fft_scalar*)samples.data(), fft_out); |
| 437 | |||
| 438 | // Log-bin into 16 octave-ish buckets, mirroring WLED's binning. The loop | ||
| 439 | // below fills buckets 0..17 (it stops when it would start an 18th) - at | ||
| 440 | // 44.1/48kHz the top ~20 bins land in 17 - and only 0..15 are reported. | ||
| 441 | 2470 | std::array<float, 18> res{}; | |
| 442 | 2470 | float rate = sampleRate; | |
| 443 | 2470 | int curBucket = 0; | |
| 444 | 2470 | double freqnext = 440.0 * exp2f(((float)((1 + 1) * 8) - 69.0) / 12.0); | |
| 445 | 2470 | int end = freqnext * (float)NUM_SAMPLES / rate; | |
| 446 | 2470 | float binHzRange = rate / (float)NUM_SAMPLES; | |
| 447 | |||
| 448 | 2470 | float maxValue = 0; | |
| 449 | 2470 | int maxBin = 0; | |
| 450 | 2470 | int maxBucket = 0; | |
| 451 |
2/2✓ Branch 0 taken 1264640 times.
✓ Branch 1 taken 2470 times.
|
1267110 | for (int bin = 0; bin < (NUM_SAMPLES / 2); ++bin) { |
| 452 |
2/2✓ Branch 0 taken 41990 times.
✓ Branch 1 taken 1222650 times.
|
1264640 | if (bin > end) { |
| 453 | 41990 | curBucket++; | |
| 454 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 41990 times.
|
41990 | if (curBucket == 18) break; |
| 455 | 41990 | freqnext = 440.0 * exp2f(((double)((curBucket + 1) * 8) - 69.0) / 12.0); | |
| 456 | 41990 | end = freqnext * (double)NUM_SAMPLES / rate; | |
| 457 | } | ||
| 458 | 1264640 | float nv = sqrtf(fft_out[bin].r * fft_out[bin].r + fft_out[bin].i * fft_out[bin].i); | |
| 459 |
2/2✓ Branch 0 taken 23327 times.
✓ Branch 1 taken 1241313 times.
|
1264640 | if (nv > maxValue) { |
| 460 | 23327 | maxValue = nv; | |
| 461 | 23327 | maxBin = bin; | |
| 462 | 23327 | maxBucket = curBucket; | |
| 463 | } | ||
| 464 | 1264640 | res[curBucket] = std::max(res[curBucket], nv); | |
| 465 | } | ||
| 466 | |||
| 467 | 2470 | float maxRV = 0; | |
| 468 |
2/2✓ Branch 0 taken 39520 times.
✓ Branch 1 taken 2470 times.
|
41990 | for (int x = 0; x < 16; x++) { |
| 469 | 39520 | int v2 = round(log10(res[x]) * 120.0); | |
| 470 |
2/2✓ Branch 0 taken 256 times.
✓ Branch 1 taken 39264 times.
|
39520 | if (v2 > 255) v2 = 255; |
| 471 |
2/2✓ Branch 0 taken 9342 times.
✓ Branch 1 taken 30178 times.
|
39520 | if (v2 < 0) v2 = 0; |
| 472 | 39520 | out.fftResult[x] = static_cast<uint8_t>(v2); | |
| 473 |
2/2✓ Branch 0 taken 18727 times.
✓ Branch 1 taken 20793 times.
|
39520 | if (res[x] > maxRV) maxRV = res[x]; |
| 474 | } | ||
| 475 | |||
| 476 | 2470 | out.FFT_MajorPeak = (maxBin * binHzRange) + (binHzRange / 2); | |
| 477 | 2470 | out.FFT_Magnitude = maxRV; | |
| 478 | |||
| 479 | // RMS / peak amplitude for the volume fields. | ||
| 480 | 2470 | float peakAmp = 0.0f; | |
| 481 |
2/2✓ Branch 0 taken 2529280 times.
✓ Branch 1 taken 2470 times.
|
2531750 | for (int i = 0; i < NUM_SAMPLES; ++i) { |
| 482 | 2529280 | float a = std::fabs(samples[i]); | |
| 483 |
2/2✓ Branch 0 taken 69458 times.
✓ Branch 1 taken 2459822 times.
|
2529280 | if (a > peakAmp) peakAmp = a; |
| 484 | } | ||
| 485 | 2470 | float instantVolume = std::min(255.0f, peakAmp * 255.0f); | |
| 486 | |||
| 487 | static float smoothedVolume = 0.0f; | ||
| 488 | 2470 | smoothedVolume = 0.7f * smoothedVolume + 0.3f * instantVolume; | |
| 489 | |||
| 490 | // Beat detection — same approach as the WLED audioreactive usermod: | ||
| 491 | // an above-threshold step over the smoothed baseline marks a beat, | ||
| 492 | // held for 50ms with a 100ms refractory window so receivers don't | ||
| 493 | // miss it even with slower poll cadences. | ||
| 494 | static uint64_t lastPeakMs = 0; | ||
| 495 |
1/2✓ Branch 0 taken 2470 times.
✗ Branch 1 not taken.
|
2470 | uint64_t nowMsLocal = GetMonotonicTimeMS(); |
| 496 | 2470 | uint8_t peakOut = 0; | |
| 497 |
2/2✓ Branch 0 taken 2450 times.
✓ Branch 1 taken 20 times.
|
2470 | if (instantVolume > 12.0f && |
| 498 |
2/2✓ Branch 0 taken 12 times.
✓ Branch 1 taken 2438 times.
|
2450 | instantVolume > smoothedVolume + 25.0f && |
| 499 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 8 times.
|
12 | (nowMsLocal - lastPeakMs) >= 100) { |
| 500 | 4 | lastPeakMs = nowMsLocal; | |
| 501 | } | ||
| 502 |
2/2✓ Branch 0 taken 19 times.
✓ Branch 1 taken 2451 times.
|
2470 | if (nowMsLocal - lastPeakMs < 50) peakOut = 1; |
| 503 | |||
| 504 | 2470 | out.volumeSmth = smoothedVolume; | |
| 505 | 2470 | out.volumeRaw = static_cast<uint16_t>(instantVolume); | |
| 506 | 2470 | out.samplePeak = peakOut; | |
| 507 | 2470 | } | |
| 508 | |||
| 509 | 2346 | WS2812FXExt::WS2812FXExt() : | |
| 510 | 2346 | WS2812FX() { | |
| 511 |
1/2✓ Branch 0 taken 2346 times.
✗ Branch 1 not taken.
|
2346 | pushCurrent(this); |
| 512 |
1/2✓ Branch 0 taken 2346 times.
✗ Branch 1 not taken.
|
2346 | finalizeInit(); |
| 513 |
1/2✓ Branch 0 taken 2346 times.
✗ Branch 1 not taken.
|
2346 | popCurrent(); |
| 514 | 2346 | } | |
| 515 | 1196 | WS2812FXExt::WS2812FXExt(PixelOverlayModel* m, int map, int b, | |
| 516 | uint8_t mode, uint8_t s, uint8_t i, uint8_t pal, | ||
| 517 | uint32_t c1, uint32_t c2, uint32_t c3, | ||
| 518 | uint8_t custom1, uint8_t custom2, uint8_t custom3, | ||
| 519 | int check1, int check2, int check3, | ||
| 520 | const std::string& text, | ||
| 521 | 1196 | const PolarMode polarMode) : | |
| 522 | 1196 | WS2812FX(), model(m), mapping(map), brightness(b) { | |
| 523 |
1/2✓ Branch 0 taken 1196 times.
✗ Branch 1 not taken.
|
1196 | WS2812FXExt::pushCurrent(this); |
| 524 | |||
| 525 | // A Radial/Angular mapping asks the model for a polar view of its buffer. | ||
| 526 | // It can decline -- no xLights layout on this player, this model missing | ||
| 527 | // from it, too little geometry -- and then this behaves as Horizontal, | ||
| 528 | // which is what an unrecognised mapping has always done. | ||
| 529 |
2/2✓ Branch 0 taken 23 times.
✓ Branch 1 taken 1173 times.
|
1196 | if (polarMode != PolarMode::None) { |
| 530 |
1/2✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
|
23 | polar = m->getPolarMap(polarMode); |
| 531 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 17 times.
|
23 | if (polar == nullptr) { |
| 532 | 6 | mapping = 0; | |
| 533 | } | ||
| 534 | } | ||
| 535 | 1196 | Panel p; | |
| 536 | int segW, segH; | ||
| 537 |
2/2✓ Branch 0 taken 17 times.
✓ Branch 1 taken 1179 times.
|
1196 | if (polar) { |
| 538 | // The segment IS the polar buffer: x = radius, y = angle (or the other | ||
| 539 | // way round for the Angular modes). getLength() below must agree with | ||
| 540 | // this or effects index past the LUT. | ||
| 541 | 17 | segW = polar->width; | |
| 542 | 17 | segH = polar->height; | |
| 543 |
4/4✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1176 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 2 times.
|
1179 | } else if (mapping == 0 || mapping == 2) { |
| 544 | 1177 | segW = m->getWidth(); | |
| 545 | 1177 | segH = m->getHeight(); | |
| 546 | } else { | ||
| 547 | 2 | segW = m->getHeight(); | |
| 548 | 2 | segH = m->getWidth(); | |
| 549 | } | ||
| 550 | // WLED takes a 1 x N segment as one pixel long; N x 1 is the same to busPixelXY(). | ||
| 551 |
3/4✓ Branch 0 taken 6 times.
✓ Branch 1 taken 1190 times.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
|
1196 | if (segW == 1 && segH > 1) { |
| 552 | 6 | std::swap(segW, segH); | |
| 553 | } | ||
| 554 |
1/2✓ Branch 0 taken 1196 times.
✗ Branch 1 not taken.
|
1196 | _segments.push_back(Segment(0, segW, 0, segH)); |
| 555 | 1196 | p.width = segW; | |
| 556 | 1196 | p.height = segH; | |
| 557 |
3/4✓ Branch 0 taken 1196 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 961 times.
✓ Branch 3 taken 235 times.
|
1196 | isMatrix = segW > 1 && segH > 1; |
| 558 |
1/2✓ Branch 0 taken 1196 times.
✗ Branch 1 not taken.
|
1196 | _segments[0].setMode(mode, true); |
| 559 | 1196 | _segments[0].speed = s; | |
| 560 | 1196 | _segments[0].intensity = i; | |
| 561 | 1196 | _segments[0].palette = pal; | |
| 562 | 1196 | _segments[0].custom1 = custom1; | |
| 563 | 1196 | _segments[0].custom2 = custom2; | |
| 564 | 1196 | _segments[0].custom3 = custom3; | |
| 565 | 1196 | _segments[0].check1 = check1; | |
| 566 | 1196 | _segments[0].check2 = check2; | |
| 567 | 1196 | _segments[0].check3 = check3; | |
| 568 |
3/4✓ Branch 0 taken 1196 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 1194 times.
|
1196 | if (text != "") { |
| 569 | // Not setName(): that caps the name at WLED_MAX_SEGNAME_LEN, and the | ||
| 570 | // Scrolling Text effect takes its message from here. malloc, not | ||
| 571 | // new[], because ~Segment free()s it. | ||
| 572 | 2 | _segments[0].name = strdup(text.c_str()); | |
| 573 | } | ||
| 574 |
1/2✓ Branch 0 taken 1196 times.
✗ Branch 1 not taken.
|
1196 | _segments[0].refreshLightCapabilities(); |
| 575 | |||
| 576 |
1/2✓ Branch 0 taken 1196 times.
✗ Branch 1 not taken.
|
1196 | _segments[0].setColor(0, c1); |
| 577 |
1/2✓ Branch 0 taken 1196 times.
✗ Branch 1 not taken.
|
1196 | _segments[0].setColor(1, c2); |
| 578 |
1/2✓ Branch 0 taken 1196 times.
✗ Branch 1 not taken.
|
1196 | _segments[0].setColor(2, c3); |
| 579 | |||
| 580 | 1196 | um_data.u_size = UDATA_ELEMENTS; | |
| 581 | 1196 | um_data.u_type = &udata_types[0]; | |
| 582 | 1196 | um_data.u_data = &udata_ptrs[0]; | |
| 583 | 1196 | um_data.u_data[0] = &volumeSmth; //*used (New) | |
| 584 | 1196 | um_data.u_type[0] = UMT_FLOAT; | |
| 585 | 1196 | um_data.u_data[1] = &volumeRaw; // used (New) | |
| 586 | 1196 | um_data.u_type[1] = UMT_UINT16; | |
| 587 | 1196 | um_data.u_data[2] = &fftResult[0]; //*used (Blurz, DJ Light, Noisemove, GEQ_base, 2D Funky Plank, Akemi) | |
| 588 | 1196 | um_data.u_type[2] = UMT_BYTE_ARR; | |
| 589 | 1196 | um_data.u_data[3] = &samplePeak; //*used (Puddlepeak, Ripplepeak, Waterfall) | |
| 590 | 1196 | um_data.u_type[3] = UMT_BYTE; | |
| 591 | 1196 | um_data.u_data[4] = &FFT_MajorPeak; //*used (Ripplepeak, Freqmap, Freqmatrix, Freqpixels, Freqwave, Gravfreq, Rocktaves, Waterfall) | |
| 592 | 1196 | um_data.u_type[4] = UMT_FLOAT; | |
| 593 | 1196 | um_data.u_data[5] = &my_magnitude; // used (New) | |
| 594 | 1196 | um_data.u_type[5] = UMT_FLOAT; | |
| 595 | 1196 | um_data.u_data[6] = &maxVol; // assigned in effect function from UI element!!! (Puddlepeak, Ripplepeak, Waterfall) | |
| 596 | 1196 | um_data.u_type[6] = UMT_BYTE; | |
| 597 | 1196 | um_data.u_data[7] = &binNum; // assigned in effect function from UI element!!! (Puddlepeak, Ripplepeak, Waterfall) | |
| 598 | 1196 | um_data.u_type[7] = UMT_BYTE; | |
| 599 | |||
| 600 | /* | ||
| 601 | micDataReal = 0.0f; | ||
| 602 | volumeRaw = 0; | ||
| 603 | volumeSmth = 0; | ||
| 604 | sampleAgc = 0; | ||
| 605 | sampleAvg = 0; | ||
| 606 | sampleRaw = 0; | ||
| 607 | rawSampleAgc = 0; | ||
| 608 | my_magnitude = 0; | ||
| 609 | FFT_Magnitude = 0; | ||
| 610 | FFT_MajorPeak = 1; | ||
| 611 | multAgc = 1; | ||
| 612 | // reset FFT data | ||
| 613 | memset(fftCalc, 0, sizeof(fftCalc)); | ||
| 614 | memset(fftAvg, 0, sizeof(fftAvg)); | ||
| 615 | memset(fftResult, 0, sizeof(fftResult)); | ||
| 616 | for (int i = (init ? 0 : 1); i < NUM_GEQ_CHANNELS; i += 2) | ||
| 617 | fftResult[i] = 16; // make a tiny pattern | ||
| 618 | inputLevel = 128; | ||
| 619 | autoResetPeak(); | ||
| 620 | */ | ||
| 621 | |||
| 622 |
1/2✓ Branch 0 taken 1196 times.
✗ Branch 1 not taken.
|
1196 | panel.push_back(p); |
| 623 | // Already current (pushed at the top). This used to push a second time, | ||
| 624 | // which made the strip its own parent: the two pops that followed (here | ||
| 625 | // and in the caller) left it current on this thread forever, dangling | ||
| 626 | // once the effect was deleted, and clearInstance() then looped on it. | ||
| 627 |
1/2✓ Branch 0 taken 1196 times.
✗ Branch 1 not taken.
|
1196 | finalizeInit(); |
| 628 | 1196 | ownMaxWidth = Segment::maxWidth; | |
| 629 | 1196 | ownMaxHeight = Segment::maxHeight; | |
| 630 |
1/2✓ Branch 0 taken 1196 times.
✗ Branch 1 not taken.
|
1196 | popCurrent(); |
| 631 | 1196 | } | |
| 632 | |||
| 633 | 1628 | WS2812FXExt::~WS2812FXExt() { | |
| 634 | 1628 | um_data.u_type = nullptr; | |
| 635 | 1628 | um_data.u_data = nullptr; | |
| 636 | 1628 | } | |
| 637 | |||
| 638 | thread_local WS2812FXExt* currentStrip = nullptr; | ||
| 639 | 15768483 | WS2812FXExt* WS2812FXExt::getInstance() { | |
| 640 | 15768483 | WS2812FXExt* cs = currentStrip; | |
| 641 |
2/2✓ Branch 0 taken 2346 times.
✓ Branch 1 taken 15766137 times.
|
15768483 | if (cs == nullptr) { |
| 642 |
2/6✓ Branch 0 taken 2346 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2346 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
2346 | cs = new WS2812FXExt(); |
| 643 | 2346 | currentStrip = cs; | |
| 644 | } | ||
| 645 | 15768483 | return cs; | |
| 646 | } | ||
| 647 | 458 | void WS2812FXExt::clearInstance() { | |
| 648 |
2/2✓ Branch 0 taken 458 times.
✓ Branch 1 taken 458 times.
|
916 | while (currentStrip) { |
| 649 | 458 | WS2812FXExt* e = currentStrip; | |
| 650 | 458 | currentStrip = currentStrip->parent; | |
| 651 |
1/2✓ Branch 0 taken 458 times.
✗ Branch 1 not taken.
|
458 | delete e; |
| 652 | } | ||
| 653 | 458 | } | |
| 654 | |||
| 655 | 13387 | std::recursive_mutex& WS2812FXExt::lock() { | |
| 656 | static std::recursive_mutex m; | ||
| 657 | 13387 | return m; | |
| 658 | } | ||
| 659 | |||
| 660 | 12093 | void WS2812FXExt::pushCurrent(WS2812FXExt* e) { | |
| 661 |
1/2✓ Branch 0 taken 12093 times.
✗ Branch 1 not taken.
|
12093 | if (e) { |
| 662 | 12093 | e->parent = currentStrip; | |
| 663 | 12093 | currentStrip = e; | |
| 664 |
2/2✓ Branch 0 taken 8551 times.
✓ Branch 1 taken 3542 times.
|
12093 | if (e->ownMaxWidth) { |
| 665 | // Another strip's construction may have reset the shared matrix | ||
| 666 | // geometry since this one was built; setPixelColorXY() indexes | ||
| 667 | // with it. | ||
| 668 | 8551 | Segment::maxWidth = e->ownMaxWidth; | |
| 669 | 8551 | Segment::maxHeight = e->ownMaxHeight; | |
| 670 | } | ||
| 671 | ✗ | } else if (currentStrip) { | |
| 672 | ✗ | currentStrip = currentStrip->parent; | |
| 673 | } | ||
| 674 | 12093 | } | |
| 675 | 12093 | void WS2812FXExt::popCurrent() { | |
| 676 |
1/2✓ Branch 0 taken 12093 times.
✗ Branch 1 not taken.
|
12093 | if (currentStrip) { |
| 677 | 12093 | currentStrip = currentStrip->parent; | |
| 678 | } | ||
| 679 | 12093 | } | |
| 680 | |||
| 681 | 6059 | PixelOverlayModel* currentBusModel() { | |
| 682 |
1/2✓ Branch 0 taken 6059 times.
✗ Branch 1 not taken.
|
6059 | return currentStrip ? currentStrip->model : nullptr; |
| 683 | } | ||
| 684 | |||
| 685 | // RGBW capability is per-overlay-model, since FPP overlay models can be | ||
| 686 | // RGB (3bpp) or RGBW (4bpp). Whichever effect happens to be running | ||
| 687 | // queries the current strip's underlying model to decide. | ||
| 688 | 6059 | bool Bus::hasWhite() const { | |
| 689 | 6059 | auto* m = currentBusModel(); | |
| 690 |
4/4✓ Branch 0 taken 3713 times.
✓ Branch 1 taken 2346 times.
✓ Branch 2 taken 1323 times.
✓ Branch 3 taken 2390 times.
|
6059 | return m && m->getBytesPerPixel() == 4; |
| 691 | } | ||
| 692 | |||
| 693 | // On RGBW models, default to AUTO_BRIGHTER so every existing WLED | ||
| 694 | // effect benefits from the white channel without needing to know | ||
| 695 | // about W. Effects that DO write W explicitly (Slow Transition's | ||
| 696 | // W(SEGCOLOR(0)) reads, future RGBW-aware additions) still work | ||
| 697 | // because the auto-white pass only fills W when the effect left it | ||
| 698 | // at zero. | ||
| 699 | 1321 | uint8_t Bus::getAutoWhiteMode() const { | |
| 700 |
1/2✓ Branch 0 taken 1321 times.
✗ Branch 1 not taken.
|
1321 | return hasWhite() ? RGBW_MODE_AUTO_BRIGHTER : RGBW_MODE_MANUAL_ONLY; |
| 701 | } | ||
| 702 | |||
| 703 | 8280 | int Bus::getLength() const { | |
| 704 | // Must match the Segment the WS2812FXExt constructor built, or an effect | ||
| 705 | // walks off the end of the LUT. | ||
| 706 |
2/2✓ Branch 0 taken 51 times.
✓ Branch 1 taken 8229 times.
|
8280 | if (currentStrip->polar) { |
| 707 | 51 | return (int)currentStrip->polar->indexToCell.size(); | |
| 708 | } | ||
| 709 |
2/2✓ Branch 0 taken 3537 times.
✓ Branch 1 taken 4692 times.
|
8229 | if (currentStrip->model) { |
| 710 | 3537 | return currentStrip->model->getWidth() * currentStrip->model->getHeight(); | |
| 711 | } | ||
| 712 | 4692 | return 1; | |
| 713 | } | ||
| 714 | /* | ||
| 715 | * Turn a WLED linear index into a buffer cell of the current model. | ||
| 716 | * | ||
| 717 | * With a polar mapping this is one LUT lookup -- cheaper than the div/mod pair | ||
| 718 | * the ordinary mappings need -- because the ORDER of the LUT already encodes | ||
| 719 | * the layout. Returns false for a polar cell no pixel occupies; polar binning | ||
| 720 | * is inherently sparse and those cells simply are not painted. | ||
| 721 | */ | ||
| 722 | 3164209 | static inline bool busPixelXY(int i, int& x, int& y) { | |
| 723 | 3164209 | int w = currentStrip->model->getWidth(); | |
| 724 | |||
| 725 |
2/2✓ Branch 0 taken 8616 times.
✓ Branch 1 taken 3155593 times.
|
3164209 | if (currentStrip->polar) { |
| 726 | 8616 | const PolarBufferMap* pm = currentStrip->polar; | |
| 727 |
3/6✓ Branch 0 taken 8616 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 8616 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 8616 times.
|
8616 | if (i < 0 || (size_t)i >= pm->indexToCell.size()) { |
| 728 | ✗ | return false; | |
| 729 | } | ||
| 730 | 8616 | uint32_t cell = pm->indexToCell[i]; | |
| 731 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8616 times.
|
8616 | if (cell == PolarBufferMap::Empty) { |
| 732 | ✗ | return false; | |
| 733 | } | ||
| 734 | 8616 | x = cell % w; | |
| 735 | 8616 | y = cell / w; | |
| 736 | 8616 | return true; | |
| 737 | } | ||
| 738 | |||
| 739 | 3155593 | int h = currentStrip->model->getHeight(); | |
| 740 | 3155593 | int mapping = currentStrip->mapping; | |
| 741 |
4/4✓ Branch 0 taken 3155113 times.
✓ Branch 1 taken 480 times.
✓ Branch 2 taken 1992 times.
✓ Branch 3 taken 3153121 times.
|
3155593 | if (mapping == 1 || mapping == 3) { |
| 742 | 2472 | y = i % h; | |
| 743 | 2472 | x = i / h; | |
| 744 | } else { | ||
| 745 | 3153121 | x = i % w; | |
| 746 | 3153121 | y = i / w; | |
| 747 | } | ||
| 748 | |||
| 749 |
2/2✓ Branch 0 taken 648 times.
✓ Branch 1 taken 3154945 times.
|
3155593 | if (mapping == 2) { |
| 750 | 648 | y = h - y - 1; | |
| 751 | } | ||
| 752 |
2/2✓ Branch 0 taken 1992 times.
✓ Branch 1 taken 3153601 times.
|
3155593 | if (mapping == 3) { |
| 753 | 1992 | x = w - x - 1; | |
| 754 | } | ||
| 755 | 3155593 | return true; | |
| 756 | } | ||
| 757 | |||
| 758 | 791136 | uint32_t Bus::getPixelColor(int i) const { | |
| 759 | 791136 | int x = 0, y = 0; | |
| 760 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 791136 times.
|
791136 | if (!busPixelXY(i, x, y)) { |
| 761 | ✗ | return 0; | |
| 762 | } | ||
| 763 | |||
| 764 | int r, g, b, wh; | ||
| 765 |
1/2✓ Branch 0 taken 791136 times.
✗ Branch 1 not taken.
|
791136 | currentStrip->model->getOverlayPixelValue(x, y, r, g, b, wh); |
| 766 | |||
| 767 | // Undo setPixelColor()'s brightness scale, as WLED does, or effects that | ||
| 768 | // read pixels back apply it again each frame. Rounded up, so a pixel | ||
| 769 | // written back unchanged stores the same value. | ||
| 770 | 791136 | int brightness = currentStrip->brightness; | |
| 771 |
2/2✓ Branch 0 taken 350087 times.
✓ Branch 1 taken 441049 times.
|
791136 | if (brightness != 128) { |
| 772 |
2/2✓ Branch 0 taken 117862 times.
✓ Branch 1 taken 232225 times.
|
350087 | if (brightness <= 0) { |
| 773 | 117862 | return 0; | |
| 774 | } | ||
| 775 | // No divide per pixel: ARMv6 (Pi Zero) and the BBB have none in hardware. | ||
| 776 | static thread_local int lutBrightness = -1; | ||
| 777 | static thread_local uint8_t lut[256]; | ||
| 778 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 232221 times.
|
232225 | if (lutBrightness != brightness) { |
| 779 |
2/2✓ Branch 0 taken 1024 times.
✓ Branch 1 taken 4 times.
|
1028 | for (int v = 0; v < 256; v++) { |
| 780 | 1024 | lut[v] = std::min((v * 128 + brightness - 1) / brightness, 255); | |
| 781 | } | ||
| 782 | 4 | lutBrightness = brightness; | |
| 783 | } | ||
| 784 | 232225 | r = lut[r & 0xFF]; | |
| 785 | 232225 | g = lut[g & 0xFF]; | |
| 786 | 232225 | b = lut[b & 0xFF]; | |
| 787 | 232225 | wh = lut[wh & 0xFF]; | |
| 788 | } | ||
| 789 | 673274 | return (wh << 24) | (r << 16) | (g << 8) | b; | |
| 790 | } | ||
| 791 | 2373073 | void Bus::setPixelColor(int i, uint32_t c) { | |
| 792 | 2373073 | int x = 0, y = 0; | |
| 793 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2373073 times.
|
2373073 | if (!busPixelXY(i, x, y)) { |
| 794 | ✗ | return; | |
| 795 | } | ||
| 796 | 2373073 | int r = R(c); | |
| 797 | 2373073 | int g = G(c); | |
| 798 | 2373073 | int b = B(c); | |
| 799 | 2373073 | int wh = W(c); | |
| 800 | |||
| 801 | // RGBW auto-white extraction. Almost every WLED effect emits pure | ||
| 802 | // RGB and leaves W=0; on RGBW overlay models we synthesize a W | ||
| 803 | // value from the RGB so the white channel actually drives the | ||
| 804 | // white LED. The bus reports its mode via getAutoWhiteMode(): | ||
| 805 | // AUTO_BRIGHTER - W = min(R,G,B), RGB unchanged (brighter overall) | ||
| 806 | // AUTO_ACCURATE - W = min(R,G,B), subtract from RGB (color-faithful) | ||
| 807 | // MAX - W = max(R,G,B), best for white-only LEDs | ||
| 808 | // MANUAL_ONLY - leave W as-is (effect controls it directly) | ||
| 809 |
7/8✓ Branch 0 taken 2372946 times.
✓ Branch 1 taken 127 times.
✓ Branch 2 taken 2372946 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1320 times.
✓ Branch 5 taken 2371626 times.
✓ Branch 6 taken 1320 times.
✓ Branch 7 taken 2371753 times.
|
2373073 | if (wh == 0 && currentStrip->model && currentStrip->model->getBytesPerPixel() == 4) { |
| 810 |
1/2✓ Branch 0 taken 1320 times.
✗ Branch 1 not taken.
|
1320 | uint8_t aw = BusManager::bus.getAutoWhiteMode(); |
| 811 |
1/2✓ Branch 0 taken 1320 times.
✗ Branch 1 not taken.
|
1320 | int mn = std::min({r, g, b}); |
| 812 |
1/2✓ Branch 0 taken 1320 times.
✗ Branch 1 not taken.
|
1320 | if (aw == RGBW_MODE_AUTO_BRIGHTER) { |
| 813 | 1320 | wh = mn; | |
| 814 | ✗ | } else if (aw == RGBW_MODE_AUTO_ACCURATE) { | |
| 815 | ✗ | wh = mn; | |
| 816 | ✗ | r -= mn; g -= mn; b -= mn; | |
| 817 | ✗ | } else if (aw == RGBW_MODE_MAX) { | |
| 818 | ✗ | wh = std::max({r, g, b}); | |
| 819 | } | ||
| 820 | } | ||
| 821 | |||
| 822 | 2373073 | int brightness = currentStrip->brightness; | |
| 823 | 2373073 | r = r * brightness / 128; | |
| 824 | 2373073 | g = g * brightness / 128; | |
| 825 | 2373073 | b = b * brightness / 128; | |
| 826 | 2373073 | wh = wh * brightness / 128; | |
| 827 |
1/2✓ Branch 0 taken 2373073 times.
✗ Branch 1 not taken.
|
2373073 | currentStrip->model->setOverlayPixelValue(x, y, min(r, 255), min(g, 255), min(b, 255), min(wh, 255)); |
| 828 | } | ||
| 829 | |||
| 830 | 1441 | bool UsermodManager::getUMData(um_data_t** um_data, uint8_t mod_id) { | |
| 831 | 1441 | return false; | |
| 832 | } | ||
| 833 | ✗ | CRGB getCRGBForBand(int x, uint8_t* fftResult, int pal) { | |
| 834 | CRGB value; | ||
| 835 | ✗ | CHSV hsv; | |
| 836 | ✗ | if (pal == 71) { // bit hacky to use palette id here, but don't want to litter the code with lots of different methods. TODO: add enum for palette creation type | |
| 837 | ✗ | if (x == 1) { | |
| 838 | ✗ | value = CRGB(fftResult[10] / 2, fftResult[4] / 2, fftResult[0] / 2); | |
| 839 | ✗ | } else if (x == 255) { | |
| 840 | ✗ | value = CRGB(fftResult[10] / 2, fftResult[0] / 2, fftResult[4] / 2); | |
| 841 | } else { | ||
| 842 | ✗ | value = CRGB(fftResult[0] / 2, fftResult[4] / 2, fftResult[10] / 2); | |
| 843 | } | ||
| 844 | ✗ | } else if (pal == 72) { | |
| 845 | ✗ | int b = map(x, 0, 255, 0, 8); // convert palette position to lower half of freq band | |
| 846 | ✗ | hsv = CHSV(fftResult[b], 255, map(fftResult[b], 0, 255, 30, 255)); // pick hue | |
| 847 | ✗ | hsv2rgb_rainbow(hsv, value); // convert to R,G,B | |
| 848 | } | ||
| 849 | ✗ | return value; | |
| 850 | } | ||
| 851 | ✗ | void WS2812FXExt::processSamples(std::array<float, NUM_SAMPLES>& samples, int sampleRate) { | |
| 852 | // Delegate the FFT + binning + RMS + beat work to the free helper | ||
| 853 | // so WLEDAudioSync's broadcast thread can run the same pipeline | ||
| 854 | // without holding a WS2812FXExt instance. We then copy the result | ||
| 855 | // into our own per-instance um_data fields for FX.cpp to read. | ||
| 856 | ✗ | AudioFrame f{}; | |
| 857 | ✗ | computeAudioFrame(samples, sampleRate, f); | |
| 858 | |||
| 859 | ✗ | uint8_t* dstFft = (uint8_t*)um_data.u_data[2]; | |
| 860 | ✗ | std::memcpy(dstFft, f.fftResult, 16); | |
| 861 | ✗ | FFT_MajorPeak = f.FFT_MajorPeak; | |
| 862 | ✗ | my_magnitude = f.FFT_Magnitude; | |
| 863 | ✗ | volumeSmth = f.volumeSmth; | |
| 864 | ✗ | volumeRaw = f.volumeRaw; | |
| 865 | ✗ | samplePeak = f.samplePeak; | |
| 866 | |||
| 867 | // maxVol/binNum aren't part of AudioFrame (they're a couple of | ||
| 868 | // decorative scalars only a few effects read); recompute cheaply. | ||
| 869 | ✗ | int maxV = 0, maxBucket = 0; | |
| 870 | ✗ | for (int x = 0; x < 16; x++) { | |
| 871 | ✗ | if (f.fftResult[x] > maxV) { maxV = f.fftResult[x]; maxBucket = x; } | |
| 872 | } | ||
| 873 | ✗ | maxVol = maxV; | |
| 874 | ✗ | binNum = maxBucket; | |
| 875 | |||
| 876 | ✗ | if (volumeSmth < 1) my_magnitude = 0.001f; | |
| 877 | ✗ | } | |
| 878 | // Currently 4 types defined, to be fine tuned and new types added | ||
| 879 | typedef enum UM_SoundSimulations { | ||
| 880 | UMS_BeatSin = 0, | ||
| 881 | UMS_WeWillRockYou, | ||
| 882 | UMS_10_13, | ||
| 883 | UMS_14_3 | ||
| 884 | } um_soundSimulations_t; | ||
| 885 | 1441 | um_data_t* WS2812FXExt::getAudioSamples(int simulationId) { | |
| 886 | // Network audio source has highest priority — when WLEDAudioSync | ||
| 887 | // is in receive mode and we have a recent packet, drop the local | ||
| 888 | // FFT entirely and use the network values. If no recent packet, | ||
| 889 | // fall through to the normal local-audio path. | ||
| 890 |
3/4✓ Branch 0 taken 1441 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 498 times.
✓ Branch 3 taken 943 times.
|
1441 | if (WLEDAudioSync::INSTANCE.InjectCachedFrame( |
| 891 | 1441 | fftResult.data(), volumeSmth, volumeRaw, samplePeak, | |
| 892 |
0/2✗ Branch 0 not taken.
✗ Branch 1 not taken.
|
1441 | my_magnitude, FFT_MajorPeak)) { |
| 893 | 498 | return &um_data; | |
| 894 | } | ||
| 895 | |||
| 896 | std::array<float, NUM_SAMPLES> samples; | ||
| 897 | 943 | int sampleRate = 0; | |
| 898 |
2/4✓ Branch 0 taken 943 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 943 times.
|
943 | if (WLED_SOUND_SOURCE.getAudioSamples(samples, sampleRate)) { |
| 899 | ✗ | processSamples(samples, sampleRate); | |
| 900 | } else { | ||
| 901 | // arrays | ||
| 902 |
1/2✓ Branch 0 taken 943 times.
✗ Branch 1 not taken.
|
943 | auto ms = GetMonotonicTimeMS(); |
| 903 | |||
| 904 |
2/4✓ Branch 0 taken 920 times.
✓ Branch 1 taken 23 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
943 | switch (simulationId) { |
| 905 | 920 | default: | |
| 906 | case UMS_BeatSin: | ||
| 907 |
2/2✓ Branch 0 taken 14720 times.
✓ Branch 1 taken 920 times.
|
15640 | for (int i = 0; i < 16; i++) |
| 908 |
1/2✓ Branch 0 taken 14720 times.
✗ Branch 1 not taken.
|
14720 | fftResult[i] = beatsin8_t(120 / (i + 1), 0, 255); |
| 909 | // fftResult[i] = (beatsin8_t(120, 0, 255) + (256/16 * i)) % 256; | ||
| 910 | 920 | volumeSmth = fftResult[8]; | |
| 911 | 920 | break; | |
| 912 | 23 | case UMS_WeWillRockYou: | |
| 913 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 22 times.
|
23 | if (ms % 2000 < 200) { |
| 914 | 1 | volumeSmth = hw_random8(); | |
| 915 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 times.
|
6 | for (int i = 0; i < 5; i++) |
| 916 | 5 | fftResult[i] = hw_random8(); | |
| 917 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
|
22 | } else if (ms % 2000 < 400) { |
| 918 | ✗ | volumeSmth = 0; | |
| 919 | ✗ | for (int i = 0; i < 16; i++) | |
| 920 | ✗ | fftResult[i] = 0; | |
| 921 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
|
22 | } else if (ms % 2000 < 600) { |
| 922 | ✗ | volumeSmth = hw_random8(); | |
| 923 | ✗ | for (int i = 5; i < 11; i++) | |
| 924 | ✗ | fftResult[i] = hw_random8(); | |
| 925 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 19 times.
|
22 | } else if (ms % 2000 < 800) { |
| 926 | 3 | volumeSmth = 0; | |
| 927 |
2/2✓ Branch 0 taken 48 times.
✓ Branch 1 taken 3 times.
|
51 | for (int i = 0; i < 16; i++) |
| 928 | 48 | fftResult[i] = 0; | |
| 929 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 17 times.
|
19 | } else if (ms % 2000 < 1000) { |
| 930 | 2 | volumeSmth = hw_random8(); | |
| 931 |
2/2✓ Branch 0 taken 10 times.
✓ Branch 1 taken 2 times.
|
12 | for (int i = 11; i < 16; i++) |
| 932 | 10 | fftResult[i] = hw_random8(); | |
| 933 | } else { | ||
| 934 | 17 | volumeSmth = 0; | |
| 935 |
2/2✓ Branch 0 taken 272 times.
✓ Branch 1 taken 17 times.
|
289 | for (int i = 0; i < 16; i++) |
| 936 | 272 | fftResult[i] = 0; | |
| 937 | } | ||
| 938 | 23 | break; | |
| 939 | ✗ | case UMS_10_13: | |
| 940 | ✗ | for (int i = 0; i < 16; i++) | |
| 941 | ✗ | fftResult[i] = perlin8(beatsin8_t(90 / (i + 1), 0, 200) * 15 + (ms >> 10), ms >> 3); | |
| 942 | ✗ | volumeSmth = fftResult[8]; | |
| 943 | ✗ | break; | |
| 944 | ✗ | case UMS_14_3: | |
| 945 | ✗ | for (int i = 0; i < 16; i++) | |
| 946 | ✗ | fftResult[i] = perlin8(beatsin8_t(120 / (i + 1), 10, 30) * 10 + (ms >> 14), ms >> 3); | |
| 947 | ✗ | volumeSmth = fftResult[8]; | |
| 948 | ✗ | break; | |
| 949 | } | ||
| 950 | |||
| 951 | 943 | samplePeak = hw_random8() > 250; | |
| 952 | 943 | FFT_MajorPeak = 21 + (volumeSmth * volumeSmth) / 8.0f; // walk thru full range of 21hz...8200hz | |
| 953 | 943 | maxVol = 31; // this gets feedback fro UI | |
| 954 | 943 | binNum = 8; // this gets feedback fro UI | |
| 955 | 943 | volumeRaw = volumeSmth; | |
| 956 | 943 | my_magnitude = 10000.0f / 8.0f; // no idea if 10000 is a good value for FFT_Magnitude ??? | |
| 957 |
2/2✓ Branch 0 taken 67 times.
✓ Branch 1 taken 876 times.
|
943 | if (volumeSmth < 1) |
| 958 | 67 | my_magnitude = 0.001f; // noise gate closed - mute | |
| 959 | } | ||
| 960 | 943 | return &um_data; | |
| 961 | } | ||
| 962 | |||
| 963 | 1441 | um_data_t* simulateSound(uint8_t simulationId) { | |
| 964 | 1441 | return strip().getAudioSamples(simulationId); | |
| 965 | } | ||
| 966 |