WIP
This commit is contained in:
parent
d0e8eb0d5e
commit
4152ee0413
33 changed files with 27356 additions and 233 deletions
|
@ -8,16 +8,14 @@ find_package(udev REQUIRED)
|
|||
find_package(HIDAPI REQUIRED)
|
||||
pkg_check_modules(LIBUSB REQUIRED libusb-1.0)
|
||||
find_package(Threads REQUIRED)
|
||||
find_library(PULSE_FOUND NAMES pulse)
|
||||
if (PULSE_FOUND)
|
||||
set(DYNAMIC_LIBRARIES ${DYNAMIC_LIBRARIES} pulse pulse-simple)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_ENABLE_PULSE")
|
||||
set(CMAKE_LD_FLAGS "${CMAKE_LD_FLAGS} -D_ENABLE_PULSE")
|
||||
endif ()
|
||||
|
||||
find_package(JACK REQUIRED)
|
||||
|
||||
set(SOURCE_FILES
|
||||
src/VulcanoLE/API/HIDHelper.cpp
|
||||
src/VulcanoLE/API/I2C.cpp
|
||||
src/VulcanoLE/Keyboards/Vulcan121.cpp
|
||||
src/VulcanoLE/Audio/JackClient.cpp
|
||||
src/VulcanoLE/Audio/AudioGrabber.cpp
|
||||
src/VulcanoLE/Audio/FFT.cpp
|
||||
src/VulcanoLE/Audio/VisAudioRunner.cpp
|
||||
|
@ -27,6 +25,8 @@ set(SOURCE_FILES
|
|||
src/VulcanoLE/Scripts/Spectrum.cpp
|
||||
src/VulcanoLE/Scripts/PoliceLike.cpp
|
||||
src/VulcanoLE/Scripts/RainbowLine.cpp
|
||||
src/VulcanoLE/Scripts/Random.cpp
|
||||
src/VulcanoLE/Scripts/RainbowMap.cpp
|
||||
)
|
||||
set(UTILS_FILES
|
||||
src/VUtils/Logging.cpp
|
||||
|
@ -41,5 +41,5 @@ add_executable(
|
|||
VulcanoLE main.cpp
|
||||
${SOURCE_FILES} ${UTILS_FILES}
|
||||
)
|
||||
target_link_libraries(VulcanoLE fftw3 evdev hidapi-libusb udev ${CMAKE_DL_LIBS} ${DYNAMIC_LIBRARIES} Threads::Threads m
|
||||
target_link_libraries(VulcanoLE fftw3 evdev hidapi-libusb udev jack ${CMAKE_DL_LIBS} ${DYNAMIC_LIBRARIES} Threads::Threads m
|
||||
debug tbb)
|
||||
|
|
19
cmake/FindJACK.cmake
Normal file
19
cmake/FindJACK.cmake
Normal file
|
@ -0,0 +1,19 @@
|
|||
# Copyright (c) 2015 Andrew Kelley
|
||||
# This file is MIT licensed.
|
||||
# See http://opensource.org/licenses/MIT
|
||||
|
||||
# JACK_FOUND
|
||||
# JACK_INCLUDE_DIR
|
||||
# JACK_LIBRARY
|
||||
|
||||
find_path(JACK_INCLUDE_DIR NAMES jack/jack.h)
|
||||
|
||||
find_library(JACK_LIBRARY NAMES jack)
|
||||
|
||||
include(CheckLibraryExists)
|
||||
check_library_exists(jack "jack_set_port_rename_callback" "${JACK_LIBRARY}" HAVE_jack_set_port_rename_callback)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(JACK DEFAULT_MSG JACK_LIBRARY JACK_INCLUDE_DIR HAVE_jack_set_port_rename_callback)
|
||||
|
||||
mark_as_advanced(JACK_INCLUDE_DIR JACK_LIBRARY)
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace VUtils {
|
||||
class FileHandler {
|
||||
|
@ -17,6 +18,7 @@ namespace VUtils {
|
|||
static bool createDirectoryIfNotExist(const std::basic_string<char>& fileName);
|
||||
static char * getHomeDirectory();
|
||||
static std::string getFromHomeDir(const std::basic_string<char>& path);
|
||||
static std::vector<std::string> readDir(const std::basic_string<char>& path);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -6,5 +6,6 @@ namespace VUtils {
|
|||
static double lerp(double a, double b, double f);
|
||||
static double bezierBlend(double t);
|
||||
static double easeIn(double ratio);
|
||||
static double map(double x, double in_min, double in_max, double out_min, double out_max);
|
||||
};
|
||||
}
|
12
headers/VulcanoLE/API/I2C.h
Normal file
12
headers/VulcanoLE/API/I2C.h
Normal file
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
struct I2CDevice {
|
||||
std::string name;
|
||||
};
|
||||
|
||||
class I2C {
|
||||
public:
|
||||
int readDevices();
|
||||
};
|
|
@ -1,8 +1,4 @@
|
|||
#pragma once
|
||||
|
||||
#include <pulse/error.h>
|
||||
#include <pulse/pulseaudio.h>
|
||||
#include <pulse/simple.h>
|
||||
#include <unistd.h>
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
|
@ -12,14 +8,6 @@
|
|||
#include <VulcanoLE/Audio/FFT.h>
|
||||
#include <VulcanoLE/Audio/Types.h>
|
||||
|
||||
static const char recStreamName[] = "vulcanoLE";
|
||||
static const char recStreamDescription[] = "Keyboard Input Stream";
|
||||
|
||||
static const int32_t sampleRate = 44100;
|
||||
static const int32_t channels = 2;
|
||||
|
||||
static const std::string defaultMonitorPostfix = ".monitor";
|
||||
|
||||
class AudioGrabber {
|
||||
public:
|
||||
enum class ReqMode {
|
||||
|
@ -42,17 +30,8 @@ public:
|
|||
private:
|
||||
std::mutex m_mtx;
|
||||
stereoSample *m_buffer{};
|
||||
pa_simple *m_pulseaudioSimple{};
|
||||
pa_mainloop *m_pulseaudioMainloop{};
|
||||
std::string m_PulseaudioDefaultSourceName;
|
||||
void populateDefaultSourceName();
|
||||
bool openPulseaudioSource(uint32_t maxBufferSize);
|
||||
static void pulseaudioContextStateCallback(pa_context *c,
|
||||
void *userdata);
|
||||
static void pulseaudioServerInfoCallback(pa_context *context,
|
||||
const pa_server_info *i,
|
||||
void *userdata);
|
||||
void calculateRMS(stereoSample *pFrame);
|
||||
void calculatePEAK(stereoSample *pFrame);
|
||||
double m_scale = 1.0;
|
||||
int availableData = 0;
|
||||
};
|
||||
|
|
|
@ -11,14 +11,18 @@ public:
|
|||
void process(stereoSample *frame, double d);
|
||||
outputSample *getData();
|
||||
bool prepareInput(stereoSample *buffer, uint32_t sampleSize, double scale);
|
||||
double hannWindow[BUFFER_SIZE]{};
|
||||
protected:
|
||||
double *m_fftwInputLeft{};
|
||||
double *m_fftwInputRight{};
|
||||
fftw_complex *m_fftwInputLeft{};
|
||||
fftw_complex *m_fftwInputRight{};
|
||||
fftw_complex *m_fftwOutputLeft{};
|
||||
fftw_complex *m_fftwOutputRight{};
|
||||
outputSample *m_sample = new outputSample(FFT_SIZE);
|
||||
outputSample *m_sample = new outputSample(BUFFER_SIZE);
|
||||
fftw_plan m_fftwPlanLeft{};
|
||||
fftw_plan m_fftwPlanRight{};
|
||||
std::mutex m_mtx;
|
||||
size_t m_fftwResults;
|
||||
static double valueToAmp(double value);
|
||||
double times;
|
||||
static double limit;
|
||||
};
|
||||
|
|
46
headers/VulcanoLE/Audio/JackClient.h
Normal file
46
headers/VulcanoLE/Audio/JackClient.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
#pragma once
|
||||
|
||||
#include <VUtils/Environment.h>
|
||||
#include <jack/jack.h>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <VUtils/Logging.h>
|
||||
#include "Types.h"
|
||||
#include "AudioGrabber.h"
|
||||
|
||||
class JackClient {
|
||||
public:
|
||||
static JackClient &get() {
|
||||
static JackClient client;
|
||||
return client;
|
||||
}
|
||||
void start();
|
||||
static int processSamples(jack_nframes_t frames, void *arg);
|
||||
static void onExit();
|
||||
void connect(const char *port_name, jack_port_t* channel);
|
||||
void fillSamples(stereoSample *pFrame, uint32_t i) {
|
||||
std::lock_guard<std::mutex> lockGuard(guard);
|
||||
for (int j = 0; j < i; ++j) {
|
||||
pFrame[j].l = samples[j].l;
|
||||
pFrame[j].r = samples[j].r;
|
||||
}
|
||||
m_newDataAvailable = false;
|
||||
}
|
||||
bool isData();
|
||||
std::string getPort(bool isLeft);
|
||||
bool checkPort(const std::string& port);
|
||||
VUtils::Environment* env{};
|
||||
int getFrames();
|
||||
AudioGrabber *grabber;
|
||||
private:
|
||||
std::mutex guard;
|
||||
stereoSample samples[BUFFER_SIZE]{};
|
||||
stereoSample ringBuffer[BUFFER_SIZE]{};
|
||||
int samplesAdded = 0;
|
||||
jack_port_t *input_port_l = nullptr;
|
||||
jack_port_t *input_port_r = nullptr;
|
||||
bool m_newDataAvailable = false;
|
||||
jack_client_t *client = nullptr;
|
||||
jack_options_t options = jack_options_t(JackNoStartServer | JackUseExactName);
|
||||
void addFrames(jack_default_audio_sample_t *pDouble, jack_default_audio_sample_t *pDouble1, jack_nframes_t i);
|
||||
};
|
|
@ -1,6 +1,6 @@
|
|||
#pragma once
|
||||
#define FFT_SIZE 1024
|
||||
#define BUFFER_SIZE 2048
|
||||
#define FFT_SIZE 512
|
||||
#define BUFFER_SIZE 1024
|
||||
|
||||
struct stereoSampleFrame {
|
||||
float l;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <VulcanoLE/Audio/AudioGrabber.h>
|
||||
#include <VulcanoLE/Visual/VisPlugins.h>
|
||||
#include <VUtils/Environment.h>
|
||||
#include <VulcanoLE/API/I2C.h>
|
||||
|
||||
class VisAudioRunner {
|
||||
public:
|
||||
|
@ -16,5 +17,6 @@ public:
|
|||
VIZ::VisPlugins* plugins;
|
||||
bool isActive = true;
|
||||
std::thread thread;
|
||||
I2C i2c;
|
||||
VUtils::Environment *env = nullptr;
|
||||
};
|
||||
|
|
|
@ -40,6 +40,7 @@ public:
|
|||
// PLEASE MAKE SURE YOU KNOW THE LIMITS!
|
||||
int getIndexNoCheck(int row, int col);
|
||||
static void fadeOutMap(led_map* map, double factor);
|
||||
void fadeOutMapColumn(led_map* map, double factor);
|
||||
protected:
|
||||
void setupMap();
|
||||
// we need some mapping feature! rows and cols dont have the same amount of keys. so the struct needs
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
namespace VIZ {
|
||||
class PoliceLike : public VIZ {
|
||||
protected:
|
||||
int decayRate = 10;
|
||||
int decayRate = 5;
|
||||
double lastPeak = -1;
|
||||
double threshold = 15;
|
||||
double threshold = 45;
|
||||
public:
|
||||
PoliceLike(AudioGrabber *pGrabber, Vulcan121 *pVulcan121);
|
||||
~PoliceLike() override = default;
|
||||
|
|
|
@ -8,9 +8,9 @@ namespace VIZ {
|
|||
double deltaNeeded = 100000.0;
|
||||
double deltaElapsed = 0;
|
||||
rgba *colours = nullptr;
|
||||
int maxCols = 0;
|
||||
double lastValue = 0;
|
||||
double decayValue = 0;
|
||||
double ratios[4] = {1.3,1.3,1.3,1.2};
|
||||
double tailFactor = 0.3;
|
||||
public:
|
||||
RainbowLine(AudioGrabber *pGrabber, Vulcan121 *vulcan);
|
||||
|
@ -23,5 +23,7 @@ namespace VIZ {
|
|||
led_map *data = Vulcan121::createEmptyLEDMap();
|
||||
bool firstUnder = true;
|
||||
double deltaMove(double val);
|
||||
void moveLine(double val, double d);
|
||||
double factor = 1.0;
|
||||
};
|
||||
}
|
28
headers/VulcanoLE/Scripts/RainbowMap.h
Normal file
28
headers/VulcanoLE/Scripts/RainbowMap.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
|
||||
#include <VulcanoLE/Visual/VIZ.h>
|
||||
|
||||
namespace VIZ {
|
||||
class RainbowMap : public VIZ {
|
||||
int currentColumn = 0;
|
||||
double deltaNeeded = 100000.0;
|
||||
double deltaElapsed = 0;
|
||||
rgba *colours = nullptr;
|
||||
int maxCols = 0;
|
||||
double lastValue = 0;
|
||||
double decayValue = 0;
|
||||
public:
|
||||
RainbowMap(AudioGrabber *pGrabber, Vulcan121 *vulcan);
|
||||
~RainbowMap() override;
|
||||
void onSetup() override;
|
||||
void onTick(float delta) override;
|
||||
void calcNextDelta(double ratio);
|
||||
const char *name() override;
|
||||
std::string m_name = "Rainbow Map";
|
||||
led_map *data = Vulcan121::createEmptyLEDMap();
|
||||
bool firstUnder = true;
|
||||
double deltaMove(double val);
|
||||
void moveRainbow(double d);
|
||||
void updateColumn(int column, int colIndex);
|
||||
};
|
||||
}
|
26
headers/VulcanoLE/Scripts/Random.h
Normal file
26
headers/VulcanoLE/Scripts/Random.h
Normal file
|
@ -0,0 +1,26 @@
|
|||
#pragma once
|
||||
|
||||
#include <VulcanoLE/Audio/AudioGrabber.h>
|
||||
#include <VulcanoLE/Keyboards/Vulcan121.h>
|
||||
#include <VulcanoLE/Visual/VIZ.h>
|
||||
#include <vector>
|
||||
|
||||
namespace VIZ {
|
||||
class Random : public VIZ {
|
||||
protected:
|
||||
led_map *map = nullptr;
|
||||
bool isReverse{ false };
|
||||
bool emptyTicks{ false };
|
||||
public:
|
||||
Random(AudioGrabber *pGrabber, Vulcan121 *vulcan);
|
||||
~Random() override;
|
||||
void onSetup() override;
|
||||
void onTick(float delta) override;
|
||||
const char *name() override;
|
||||
float deltaElapsed;
|
||||
float deltaNeeded{ 80000 };
|
||||
int row;
|
||||
int columnCount{0}; // for last selected row all previous draw full
|
||||
int ticks;
|
||||
};
|
||||
}
|
|
@ -13,9 +13,9 @@ namespace VIZ {
|
|||
const char *name() override;
|
||||
std::string m_name = "Spectrum One";
|
||||
rgba colors[3] = {
|
||||
{ 0, 30, 150, 0 },
|
||||
{ 150, 150, 0, 0 },
|
||||
{ 150, 0, 40, 0 }
|
||||
{ 0, 10, 180, 0 },
|
||||
{ 0, 180, 0, 0 },
|
||||
{ 150, 0, 0, 0 }
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -3,9 +3,10 @@
|
|||
#include <vector>
|
||||
#include <VulcanoLE/Scripts/Spectrum.h>
|
||||
|
||||
#define VIZSIZE 4
|
||||
#define VIZSIZE 6
|
||||
|
||||
using micro = std::chrono::duration<double, std::micro>;
|
||||
using ms = std::chrono::duration<double, std::milli>;
|
||||
|
||||
namespace VIZ {
|
||||
struct VisPlugins {
|
||||
|
@ -18,11 +19,14 @@ namespace VIZ {
|
|||
~VisPlugins();
|
||||
VUtils::Environment *env{};
|
||||
protected:
|
||||
std::mutex guard;
|
||||
VIZ *viz[VIZSIZE]{};
|
||||
VIZ *currentVis{};
|
||||
Vulcan121 *keyboard{};
|
||||
AudioGrabber *grabber{};
|
||||
std::chrono::time_point<std::chrono::system_clock, micro> start;
|
||||
std::chrono::time_point<std::chrono::system_clock, ms> frameStart;
|
||||
long frames = 0;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
26674
headers/sol/sol.hpp
Normal file
26674
headers/sol/sol.hpp
Normal file
File diff suppressed because it is too large
Load diff
2
main.cpp
2
main.cpp
|
@ -5,6 +5,7 @@
|
|||
#include <csignal>
|
||||
#include <VulcanoLE/Audio/VisAudioRunner.h>
|
||||
#include <VUtils/Environment.h>
|
||||
#include <VulcanoLE/Audio/JackClient.h>
|
||||
|
||||
bool shouldRun = true;
|
||||
void my_handler(int s) {
|
||||
|
@ -22,6 +23,7 @@ int main(int argc, char **argv) {
|
|||
// Load Config...
|
||||
VUtils::Environment config{VUtils::FileHandler::getFromHomeDir("/.config/VulcanoLE/state.env").c_str()};
|
||||
config.loadFile();
|
||||
JackClient::get().env = &config;
|
||||
LOG(R"(
|
||||
|
||||
[===============================================]
|
||||
|
|
|
@ -88,4 +88,15 @@ namespace VUtils {
|
|||
std::string FileHandler::getFromHomeDir(const std::basic_string<char> &path) {
|
||||
return std::string(getHomeDirectory() + path);
|
||||
}
|
||||
|
||||
std::vector<std::string> FileHandler::readDir(const std::basic_string<char> &path) {
|
||||
std::vector<std::string> res;
|
||||
if (!isDirectory(path)) {
|
||||
ERR("Path: \"%s\" is not a directory", path.c_str());
|
||||
return res;
|
||||
}
|
||||
for (const auto &entry : std::filesystem::directory_iterator(path))
|
||||
res.push_back(entry.path());
|
||||
return res;
|
||||
}
|
||||
}
|
|
@ -14,4 +14,7 @@ namespace VUtils {
|
|||
double Math::easeIn(double ratio) {
|
||||
return ratio * ratio * ratio;
|
||||
}
|
||||
double Math::map(double x, double in_min, double in_max, double out_min, double out_max) {
|
||||
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
|
||||
}
|
||||
}
|
30
src/VulcanoLE/API/I2C.cpp
Normal file
30
src/VulcanoLE/API/I2C.cpp
Normal file
|
@ -0,0 +1,30 @@
|
|||
#include <VulcanoLE/API/I2C.h>
|
||||
#include <VUtils/FileHandler.h>
|
||||
#include <VUtils/Logging.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <linux/i2c-dev.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
// @Todo Implement Reading and creating all busses!
|
||||
int I2C::readDevices() {
|
||||
std::vector<std::string> files = VUtils::FileHandler::readDir("/sys/bus/i2c/devices/");
|
||||
if (files.empty()) {
|
||||
WARN("No I2C Devices found");
|
||||
return 1;
|
||||
}
|
||||
for (auto &file : files) {
|
||||
if (file.find("i2c-") != std::string::npos) {
|
||||
auto device = file + "/name";
|
||||
auto dev = open(device.c_str(), O_RDONLY);
|
||||
if(dev) {
|
||||
|
||||
} else {
|
||||
ERR("Cannot open device %s", device.c_str())
|
||||
}
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
|
@ -1,131 +1,25 @@
|
|||
#include <cstring>
|
||||
#include <cmath>
|
||||
#include <VulcanoLE/Audio/AudioGrabber.h>
|
||||
#include <VUtils/Logging.h>
|
||||
#include <VulcanoLE/Audio/JackClient.h>
|
||||
|
||||
|
||||
AudioGrabber::AudioGrabber() = default;
|
||||
AudioGrabber::~AudioGrabber() = default;
|
||||
|
||||
bool AudioGrabber::read(stereoSample *buffer, uint32_t bufferSize) {
|
||||
auto buffer_size_bytes = static_cast<size_t>(sizeof(stereoSample) * bufferSize);
|
||||
if (m_pulseaudioSimple == nullptr) {
|
||||
openPulseaudioSource(static_cast<uint32_t>(buffer_size_bytes));
|
||||
}
|
||||
|
||||
if (m_pulseaudioSimple != nullptr) {
|
||||
memset(buffer, 0, buffer_size_bytes);
|
||||
int32_t error_code;
|
||||
auto return_code = pa_simple_read(m_pulseaudioSimple, buffer,
|
||||
buffer_size_bytes, &error_code);
|
||||
if (return_code < 0) {
|
||||
WARN("Could not finish reading pulse Audio stream buffer\n bytes read: %d buffer\n size: %d", return_code,
|
||||
buffer_size_bytes)
|
||||
// zero out buffer
|
||||
memset(buffer, 0, buffer_size_bytes);
|
||||
pa_simple_free(m_pulseaudioSimple);
|
||||
m_pulseaudioSimple = nullptr;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Success fully read entire buffer
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void AudioGrabber::populateDefaultSourceName() {
|
||||
pa_mainloop_api *mainloop_api;
|
||||
pa_context *pulseaudio_context;
|
||||
m_pulseaudioMainloop = pa_mainloop_new();
|
||||
mainloop_api = pa_mainloop_get_api(m_pulseaudioMainloop);
|
||||
pulseaudio_context = pa_context_new(mainloop_api, "VulcanoLE device list");
|
||||
pa_context_connect(pulseaudio_context, nullptr, PA_CONTEXT_NOFLAGS,
|
||||
nullptr);
|
||||
pa_context_set_state_callback(pulseaudio_context,
|
||||
pulseaudioContextStateCallback,
|
||||
reinterpret_cast<void *>(this));
|
||||
|
||||
int ret;
|
||||
if (pa_mainloop_run(m_pulseaudioMainloop, &ret) < 0) {
|
||||
ERR("Could not open pulseaudio mainloop to find default device name: %d", ret)
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioGrabber::openPulseaudioSource(uint32_t maxBufferSize) {
|
||||
int32_t error_code = 0;
|
||||
static const pa_sample_spec sample_spec = { PA_SAMPLE_FLOAT32NE, sampleRate,
|
||||
channels };
|
||||
static const pa_buffer_attr buffer_attr = { maxBufferSize, 0, 0, 0,
|
||||
(maxBufferSize / 2) };
|
||||
populateDefaultSourceName();
|
||||
if (!m_PulseaudioDefaultSourceName.empty()) {
|
||||
m_pulseaudioSimple =
|
||||
pa_simple_new(nullptr, recStreamName, PA_STREAM_RECORD,
|
||||
m_PulseaudioDefaultSourceName.c_str(),
|
||||
recStreamDescription, &sample_spec,
|
||||
nullptr, &buffer_attr, &error_code);
|
||||
}
|
||||
if (m_pulseaudioSimple == nullptr) {
|
||||
m_pulseaudioSimple =
|
||||
pa_simple_new(nullptr, recStreamName, PA_STREAM_RECORD,
|
||||
nullptr, recStreamDescription,
|
||||
&sample_spec, nullptr, &buffer_attr, &error_code);
|
||||
}
|
||||
if (m_pulseaudioSimple == nullptr) {
|
||||
m_pulseaudioSimple =
|
||||
pa_simple_new(nullptr, recStreamName, PA_STREAM_RECORD,
|
||||
"0", recStreamDescription, &sample_spec,
|
||||
nullptr, &buffer_attr, &error_code);
|
||||
}
|
||||
if (m_pulseaudioSimple != nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ERR("Could not open pulseaudio source: %s", pa_strerror(error_code))
|
||||
return false;
|
||||
}
|
||||
|
||||
void AudioGrabber::pulseaudioContextStateCallback(pa_context *c, void *userdata) {
|
||||
switch (pa_context_get_state(c)) {
|
||||
case PA_CONTEXT_UNCONNECTED:
|
||||
case PA_CONTEXT_CONNECTING:
|
||||
case PA_CONTEXT_AUTHORIZING:
|
||||
case PA_CONTEXT_SETTING_NAME:
|
||||
break;
|
||||
|
||||
case PA_CONTEXT_READY: {
|
||||
pa_operation_unref(pa_context_get_server_info(
|
||||
c, pulseaudioServerInfoCallback, userdata));
|
||||
break;
|
||||
}
|
||||
|
||||
case PA_CONTEXT_FAILED:
|
||||
case PA_CONTEXT_TERMINATED:
|
||||
auto *src =
|
||||
reinterpret_cast<AudioGrabber *>(userdata);
|
||||
pa_mainloop_quit(src->m_pulseaudioMainloop, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioGrabber::pulseaudioServerInfoCallback(pa_context *context, const pa_server_info *i, void *userdata) {
|
||||
if (i != nullptr) {
|
||||
auto *src = reinterpret_cast<AudioGrabber *>(userdata);
|
||||
std::string name = i->default_sink_name;
|
||||
name.append(defaultMonitorPostfix);
|
||||
|
||||
src->m_PulseaudioDefaultSourceName = name;
|
||||
|
||||
// stop mainloop after finding default name
|
||||
pa_mainloop_quit(src->m_pulseaudioMainloop, 0);
|
||||
}
|
||||
auto &client = JackClient::get();
|
||||
if (!client.isData())
|
||||
return false;
|
||||
availableData = client.getFrames();
|
||||
client.fillSamples(buffer, availableData);
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioGrabber *AudioGrabber::createAudioGrabber() {
|
||||
JackClient::get().start();
|
||||
auto *grabber = new AudioGrabber();
|
||||
JackClient::get().grabber = grabber;
|
||||
return grabber;
|
||||
}
|
||||
|
||||
|
@ -140,7 +34,7 @@ void AudioGrabber::init() {
|
|||
void AudioGrabber::calculateRMS(stereoSample *pFrame) {
|
||||
float squareL = 0, meanL;
|
||||
float squareR = 0, meanR;
|
||||
for (int i = 0; i < BUFFER_SIZE; i++) {
|
||||
for (int i = 0; i < availableData; i++) {
|
||||
squareL += std::pow(pFrame[0].l * m_scale, 2);
|
||||
squareR += std::pow(pFrame[0].r * m_scale, 2);
|
||||
}
|
||||
|
@ -151,7 +45,7 @@ void AudioGrabber::calculateRMS(stereoSample *pFrame) {
|
|||
|
||||
void AudioGrabber::calculatePEAK(stereoSample *pFrame) {
|
||||
stereoSampleFrame max = { 0, 0 };
|
||||
for (int i = 0; i < BUFFER_SIZE; i++) {
|
||||
for (int i = 0; i < availableData; i++) {
|
||||
float left = std::abs(pFrame[0].l * m_scale);
|
||||
float right = std::abs(pFrame[0].r * m_scale);
|
||||
if (left > max.l)
|
||||
|
@ -188,7 +82,6 @@ bool AudioGrabber::work() {
|
|||
}
|
||||
return true;
|
||||
} else {
|
||||
DBG("Wait for Data")
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,25 +1,38 @@
|
|||
#include <VulcanoLE/Audio/FFT.h>
|
||||
#include <VUtils/Logging.h>
|
||||
#include <cmath>
|
||||
#include <VUtils/Math.h>
|
||||
|
||||
#define LIMIT 100
|
||||
|
||||
double FFT::limit = LIMIT;
|
||||
void FFT::process(stereoSample *frame, double scale) {
|
||||
std::unique_lock<std::mutex> lck(m_mtx);
|
||||
if (prepareInput(frame, FFT_SIZE, scale)) {
|
||||
for (int i = 0; i < FFT_SIZE; ++i) {
|
||||
if (prepareInput(frame, BUFFER_SIZE, scale)) {
|
||||
for (int i = 0; i < BUFFER_SIZE; ++i) {
|
||||
m_sample->leftChannel[i] = 0;
|
||||
m_sample->rightChannel[i] = 0;
|
||||
}
|
||||
// reset limit :D
|
||||
times++;
|
||||
if (times > 100 && limit != LIMIT) {
|
||||
DBG("RESET OVERSHOT TO 100")
|
||||
limit = LIMIT;
|
||||
times = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
m_fftwPlanRight = fftw_plan_dft_r2c_1d(FFT_SIZE, m_fftwInputRight, m_fftwOutputRight, FFTW_ESTIMATE);
|
||||
m_fftwPlanLeft = fftw_plan_dft_r2c_1d(FFT_SIZE, m_fftwInputLeft, m_fftwOutputLeft, FFTW_ESTIMATE);
|
||||
m_fftwPlanRight = fftw_plan_dft_1d(BUFFER_SIZE, m_fftwInputRight, m_fftwOutputRight, FFTW_FORWARD, FFTW_ESTIMATE);
|
||||
m_fftwPlanLeft = fftw_plan_dft_1d(BUFFER_SIZE, m_fftwInputLeft, m_fftwOutputLeft, FFTW_FORWARD, FFTW_ESTIMATE);
|
||||
fftw_execute(m_fftwPlanLeft);
|
||||
fftw_execute(m_fftwPlanRight);
|
||||
for (int i = 0; i < m_fftwResults; ++i) {
|
||||
double left = m_fftwOutputLeft[i][0];
|
||||
double right = m_fftwOutputRight[i][0];
|
||||
m_sample->leftChannel[i] = left;
|
||||
m_sample->rightChannel[i] = right;
|
||||
for (int i = 0; i < BUFFER_SIZE; ++i) {
|
||||
double left = std::sqrt(
|
||||
m_fftwOutputLeft[i][0] * m_fftwOutputLeft[i][0] + m_fftwOutputLeft[i][1] * m_fftwOutputLeft[i][1]);
|
||||
double right = std::sqrt(
|
||||
m_fftwOutputRight[i][0] * m_fftwOutputRight[i][0] + m_fftwOutputRight[i][1] * m_fftwOutputRight[i][1]);
|
||||
m_sample->leftChannel[i] = valueToAmp(left);
|
||||
m_sample->rightChannel[i] = valueToAmp(right);
|
||||
}
|
||||
fftw_destroy_plan(m_fftwPlanRight);
|
||||
fftw_destroy_plan(m_fftwPlanLeft);
|
||||
|
@ -34,21 +47,36 @@ outputSample *FFT::getData() {
|
|||
bool FFT::prepareInput(stereoSample *buffer, uint32_t sampleSize, double scale) {
|
||||
bool is_silent = true;
|
||||
for (size_t i = 0; i < sampleSize; ++i) {
|
||||
m_fftwInputLeft[i] = VUtils::Math::clamp(buffer[i].r * scale, -1, 1);
|
||||
m_fftwInputRight[i] = VUtils::Math::clamp(buffer[i].r * scale, -1, 1);
|
||||
if (is_silent && (m_fftwInputLeft[i] != 0 || m_fftwInputRight[i] != 0)) is_silent = false;
|
||||
m_fftwInputLeft[i][0] = VUtils::Math::clamp(buffer[i].r * scale, -1, 1) * hannWindow[i];
|
||||
m_fftwInputRight[i][0] = VUtils::Math::clamp(buffer[i].r * scale, -1, 1) * hannWindow[i];
|
||||
if (is_silent && (m_fftwInputLeft[i][0] != 0 || m_fftwInputRight[i][0] != 0)) is_silent = false;
|
||||
}
|
||||
return is_silent;
|
||||
}
|
||||
|
||||
FFT::FFT() {
|
||||
m_fftwResults = (static_cast<size_t>(BUFFER_SIZE) / 2.0) + 1;
|
||||
m_fftwInputLeft = static_cast<double *>(fftw_malloc(sizeof(double) * BUFFER_SIZE));
|
||||
m_fftwInputRight = static_cast<double *>(fftw_malloc(sizeof(double) * BUFFER_SIZE));
|
||||
m_fftwOutputLeft = static_cast<fftw_complex *>(fftw_malloc(sizeof(fftw_complex) * m_fftwResults));
|
||||
m_fftwOutputRight = static_cast<fftw_complex *>(fftw_malloc(sizeof(fftw_complex) * m_fftwResults));
|
||||
m_fftwInputLeft = static_cast<fftw_complex *>(fftw_malloc(sizeof(fftw_complex) * BUFFER_SIZE));
|
||||
m_fftwInputRight = static_cast<fftw_complex *>(fftw_malloc(sizeof(fftw_complex) * BUFFER_SIZE));
|
||||
m_fftwOutputLeft = static_cast<fftw_complex *>(fftw_malloc(sizeof(fftw_complex) * BUFFER_SIZE));
|
||||
m_fftwOutputRight = static_cast<fftw_complex *>(fftw_malloc(sizeof(fftw_complex) * BUFFER_SIZE));
|
||||
for (int i = 0; i < BUFFER_SIZE; ++i) {
|
||||
hannWindow[i] = 0.54 - 0.46 * std::cos(2 * M_PI * i / BUFFER_SIZE);
|
||||
}
|
||||
}
|
||||
FFT::~FFT() {
|
||||
delete m_sample;
|
||||
fftw_free(m_fftwInputLeft);
|
||||
fftw_free(m_fftwInputRight);
|
||||
fftw_free(m_fftwOutputLeft);
|
||||
fftw_free(m_fftwOutputRight);
|
||||
}
|
||||
double FFT::valueToAmp(double value) {
|
||||
if (value > limit) {
|
||||
double newLimit = std::floor(value) + 11;
|
||||
DBG("OVERSHOT %.2f: %.4f -> %.4f", limit, value, newLimit)
|
||||
limit = newLimit;
|
||||
}
|
||||
return VUtils::Math::clamp(value, 0, limit) / limit * 255;
|
||||
}
|
||||
|
||||
|
|
133
src/VulcanoLE/Audio/JackClient.cpp
Normal file
133
src/VulcanoLE/Audio/JackClient.cpp
Normal file
|
@ -0,0 +1,133 @@
|
|||
#include <VulcanoLE/Audio/JackClient.h>
|
||||
#include <VUtils/Logging.h>
|
||||
|
||||
int JackClient::processSamples(jack_nframes_t frames, void *arg) {
|
||||
JackClient &client = JackClient::get();
|
||||
jack_default_audio_sample_t *inl;
|
||||
jack_default_audio_sample_t *inr;
|
||||
unsigned int i;
|
||||
if (client.input_port_l == nullptr || client.input_port_r == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
inl = (jack_default_audio_sample_t *) jack_port_get_buffer(client.input_port_l, frames);
|
||||
inr = (jack_default_audio_sample_t *) jack_port_get_buffer(client.input_port_r, frames);
|
||||
client.addFrames(inl, inr, frames);
|
||||
return 0;
|
||||
}
|
||||
void JackClient::onExit() {
|
||||
DBG("SHUTDOWN JACK!")
|
||||
JackClient &jackClient = JackClient::get();
|
||||
const char **all_ports;
|
||||
unsigned int i;
|
||||
if (jackClient.input_port_l != nullptr) {
|
||||
all_ports = jack_port_get_all_connections(jackClient.client, jackClient.input_port_l);
|
||||
for (i = 0; all_ports && all_ports[i]; i++) {
|
||||
jack_disconnect(jackClient.client, all_ports[i], jack_port_name(jackClient.input_port_l));
|
||||
}
|
||||
all_ports = jack_port_get_all_connections(jackClient.client, jackClient.input_port_r);
|
||||
for (i = 0; all_ports && all_ports[i]; i++) {
|
||||
jack_disconnect(jackClient.client, all_ports[i], jack_port_name(jackClient.input_port_r));
|
||||
}
|
||||
}
|
||||
jack_client_close(jackClient.client);
|
||||
}
|
||||
|
||||
void JackClient::connect(const char *port_name, jack_port_t *channel) {
|
||||
jack_port_t *port;
|
||||
port = jack_port_by_name(client, port_name);
|
||||
if (port == nullptr) {
|
||||
ERR("Cannot Find Port: %s", port_name)
|
||||
exit(1);
|
||||
}
|
||||
LOG("Connecting %s to %s", jack_port_name(port), jack_port_name(channel))
|
||||
if (jack_connect(client, jack_port_name(port), jack_port_name(channel))) {
|
||||
ERR("Cannot connect port '%s' to '%s'", jack_port_name(port), jack_port_name(channel))
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
void JackClient::start() {
|
||||
jack_status_t status;
|
||||
client = jack_client_open("Keyboard Visual", options, &status);
|
||||
if (client == nullptr) {
|
||||
ERR("Failed to start JACK client: %d", status)
|
||||
exit(1);
|
||||
}
|
||||
DBG("Register as %s", jack_get_client_name(client))
|
||||
input_port_l = jack_port_register(client, "left", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, BUFFER_SIZE);
|
||||
input_port_r = jack_port_register(client, "right", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, BUFFER_SIZE);
|
||||
if (!input_port_l || !input_port_r) {
|
||||
ERR("Cannot Register input port left")
|
||||
exit(1);
|
||||
}
|
||||
|
||||
atexit(JackClient::onExit);
|
||||
jack_set_process_callback(client, JackClient::processSamples, nullptr);
|
||||
if (jack_activate(client)) {
|
||||
ERR("Cannot Activate Client")
|
||||
exit(1);
|
||||
}
|
||||
auto inputL = env->getEnv("input_l", "");
|
||||
auto inputR = env->getEnv("input_r", "");
|
||||
DBG("Try Using InputL: %s", inputL.c_str())
|
||||
DBG("Try Using InputR: %s", inputR.c_str())
|
||||
if (inputL.empty() || inputR.empty() || !checkPort(inputL) || !checkPort(inputR)) {
|
||||
inputL = getPort(true);
|
||||
inputR = getPort(false);
|
||||
DBG("Picking new InputL: %s", inputL.c_str())
|
||||
DBG("Picking new InputR: %s", inputR.c_str())
|
||||
env->set("input_l", inputL.c_str());
|
||||
env->set("input_r", inputR.c_str());
|
||||
}
|
||||
if (inputL.empty() || inputR.empty()) return;
|
||||
connect(inputL.c_str(), input_port_l);
|
||||
connect(inputR.c_str(), input_port_r);
|
||||
}
|
||||
bool JackClient::isData() {
|
||||
std::lock_guard<std::mutex> lockGuard(guard);
|
||||
return m_newDataAvailable;
|
||||
}
|
||||
std::string JackClient::getPort(bool isLeft) {
|
||||
std::string type = isLeft ? ":monitor_FL" : ":monitor_FR";
|
||||
auto port = jack_get_ports(client, type.c_str(), nullptr,
|
||||
JackPortIsOutput);
|
||||
if (port == nullptr)
|
||||
return "";
|
||||
return std::string(port[0]);
|
||||
}
|
||||
bool JackClient::checkPort(const std::string &port) {
|
||||
return jack_get_ports(client, port.c_str(), nullptr,
|
||||
JackPortIsOutput) != nullptr;
|
||||
}
|
||||
void JackClient::addFrames(jack_default_audio_sample_t *l, jack_default_audio_sample_t *r, jack_nframes_t i) {
|
||||
std::lock_guard<std::mutex> lockGuard(guard);
|
||||
if (!grabber)
|
||||
return;
|
||||
if (grabber->requestMode == AudioGrabber::ReqMode::FFT || grabber->requestMode == AudioGrabber::ReqMode::ALL) {
|
||||
for (int j = 0; j < i; ++j) {
|
||||
if (samplesAdded >= BUFFER_SIZE) {
|
||||
for (int k = 0; k < BUFFER_SIZE; ++k) {
|
||||
samples[k].l = ringBuffer[k].l;
|
||||
samples[k].r = ringBuffer[k].r;
|
||||
}
|
||||
m_newDataAvailable = true;
|
||||
samplesAdded = 0;
|
||||
}
|
||||
ringBuffer[samplesAdded].l = l[j];
|
||||
ringBuffer[samplesAdded].r = r[j];
|
||||
samplesAdded++;
|
||||
}
|
||||
} else {
|
||||
for (int k = 0; k < i; ++k) {
|
||||
samples[k].l = l[k];
|
||||
samples[k].r = r[k];
|
||||
}
|
||||
m_newDataAvailable = true;
|
||||
samplesAdded = (int) i;
|
||||
}
|
||||
}
|
||||
int JackClient::getFrames() {
|
||||
std::lock_guard<std::mutex> lockGuard(guard);
|
||||
return grabber->requestMode == AudioGrabber::ReqMode::FFT || grabber->requestMode == AudioGrabber::ReqMode::ALL
|
||||
? BUFFER_SIZE : samplesAdded;
|
||||
}
|
|
@ -1,7 +1,9 @@
|
|||
#include <VulcanoLE/Audio/VisAudioRunner.h>
|
||||
#include <VUtils/Logging.h>
|
||||
|
||||
VisAudioRunner::VisAudioRunner(AudioGrabber *ag, VIZ::VisPlugins *vp) : grabber(ag), plugins(vp) {}
|
||||
VisAudioRunner::VisAudioRunner(AudioGrabber *ag, VIZ::VisPlugins *vp) : grabber(ag), plugins(vp) {
|
||||
i2c.readDevices();
|
||||
}
|
||||
|
||||
VisAudioRunner::~VisAudioRunner() {
|
||||
delete plugins;
|
||||
|
@ -28,7 +30,7 @@ void VisAudioRunner::run() const {
|
|||
if (grabber->work()) {
|
||||
plugins->onTick();
|
||||
} else {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100ul));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
}
|
||||
usleep(50000);
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
#include <execution>
|
||||
#include <VulcanoLE/Keyboards/Vulcan121.h>
|
||||
#include <VUtils/Logging.h>
|
||||
#include <VUtils/Math.h>
|
||||
#include <cmath>
|
||||
|
||||
Vulcan121::Vulcan121(HIDHelper *helper)
|
||||
: m_helper(helper) {
|
||||
|
@ -26,58 +28,46 @@ Vulcan121::~Vulcan121() {
|
|||
}
|
||||
}
|
||||
|
||||
// Colors are in blocks of 12 keys (2 columns). Color parts are sorted by color e.g. the red
|
||||
// values for all 12 keys are first then come the green values etc.
|
||||
int Vulcan121::sendLedMap(led_map *src, bool deleteMap) {
|
||||
int i, k;
|
||||
rgba rgb;
|
||||
unsigned char hwmap[444]{};
|
||||
unsigned char workbuf[65];
|
||||
memset(hwmap, 0, sizeof(hwmap));
|
||||
unsigned char ledBuffer[448]{};
|
||||
memset(ledBuffer, 0, sizeof(ledBuffer));
|
||||
ledBuffer[0] = 0xa1;
|
||||
ledBuffer[1] = 0x01;
|
||||
ledBuffer[2] = 0x01;
|
||||
ledBuffer[3] = 0xb4;
|
||||
for (k = 0; k < NUM_KEYS; k++) {
|
||||
rgb = m_fixed[k] ? *(m_fixed[k]) : (src ? src->key[k] : m_colorOff);
|
||||
|
||||
rgb.r = (rgb.r > 255) ? 255 : (rgb.r < 0) ? 0 : rgb.r;
|
||||
rgb.g = (rgb.g > 255) ? 255 : (rgb.g < 0) ? 0 : rgb.g;
|
||||
rgb.b = (rgb.b > 255) ? 255 : (rgb.b < 0) ? 0 : rgb.b;
|
||||
rgb.a = (rgb.a > 255) ? 255 : (rgb.a < 0) ? 0 : rgb.a;
|
||||
// Prepare Color
|
||||
auto use = m_fixed[k] ? *(m_fixed[k]) : src ? src->key[k] : m_colorOff;
|
||||
rgb.a = (int16_t) VUtils::Math::clamp(use.a, 0, 255);
|
||||
double factor = rgb.a / 255.0;
|
||||
rgb.r *= factor;
|
||||
rgb.g *= factor;
|
||||
rgb.b *= factor;
|
||||
rgb.r = (int16_t) VUtils::Math::clamp(std::round(use.r * factor), 0, 255);
|
||||
rgb.g = (int16_t) VUtils::Math::clamp(std::round(use.g * factor), 0, 255);
|
||||
rgb.b = (int16_t) VUtils::Math::clamp(std::round(use.b * factor), 0, 255);
|
||||
|
||||
int offset = ((k / 12) * 36) + (k % 12);
|
||||
hwmap[offset + 0] = (unsigned char) rgb.r;
|
||||
hwmap[offset + 12] = (unsigned char) rgb.g;
|
||||
hwmap[offset + 24] = (unsigned char) rgb.b;
|
||||
ledBuffer[offset + 4] = (unsigned char) rgb.r;
|
||||
ledBuffer[offset + 16] = (unsigned char) rgb.g;
|
||||
ledBuffer[offset + 28] = (unsigned char) rgb.b;
|
||||
}
|
||||
|
||||
// First chunk comes with header
|
||||
workbuf[0] = 0x00;
|
||||
workbuf[1] = 0xa1;
|
||||
workbuf[2] = 0x01;
|
||||
workbuf[3] = 0x01;
|
||||
workbuf[4] = 0xb4;
|
||||
memcpy(&workbuf[5], hwmap, 60);
|
||||
if (hid_write(m_helper->m_ledDevice, workbuf, 65) != 65) {
|
||||
if (deleteMap) {
|
||||
delete src;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Six more chunks
|
||||
for (i = 1; i < 7; i++) {
|
||||
workbuf[0] = 0x00;
|
||||
memcpy(&workbuf[1], &hwmap[(i * 64) - 4], 64);
|
||||
if (hid_write(m_helper->m_ledDevice, workbuf, 65) != 65) {
|
||||
if (deleteMap) {
|
||||
unsigned char buffer[65];
|
||||
buffer[0] = 0x00;
|
||||
for (i = 0; i < 7; i++) {
|
||||
int index = i * 64;
|
||||
memcpy(&buffer[1], &ledBuffer[index], 64);
|
||||
if (hid_write(m_helper->m_ledDevice, buffer, 65) != 65) {
|
||||
ERR("Write failed")
|
||||
if (deleteMap)
|
||||
delete src;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (deleteMap) {
|
||||
if (deleteMap)
|
||||
delete src;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
int Vulcan121::sendToLEDs(rgba rgb) {
|
||||
|
@ -264,6 +254,7 @@ int Vulcan121::getIndex(int row, int col) {
|
|||
/**
|
||||
* Setup Mapping
|
||||
* They are not magic! look in the layout.md
|
||||
* @Fixme: Mapping is broken and not 100% correct for columns!
|
||||
*/
|
||||
void Vulcan121::setupMap() {
|
||||
// Row Init
|
||||
|
@ -341,3 +332,20 @@ void Vulcan121::fadeOutMap(led_map *map, double factor = 0.3) {
|
|||
i.a *= factor;
|
||||
}
|
||||
}
|
||||
|
||||
// Column FadeOut! Check if previous Col is brighter!
|
||||
void Vulcan121::fadeOutMapColumn(led_map *map, double factor) {
|
||||
for (int i = 0; i < NUM_COLS; ++i) {
|
||||
// prev check if not 0
|
||||
auto *columnA = getColumn(i);
|
||||
int16_t colorB = 0;
|
||||
if (i > 0 && map->key[columnA->index[0]].a > 0.1) {
|
||||
auto columnB = getColumn(i - 1);
|
||||
colorB = map->key[columnB->index[0]].a;
|
||||
}
|
||||
for (int j = 0; j < columnA->count; ++j) {
|
||||
auto &colorA = map->key[columnA->index[j]];
|
||||
colorA.a = colorB != 0 && colorB > colorA.a ? colorB * 1.3 : colorA.a * factor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ namespace VIZ {
|
|||
auto map = Vulcan121::createEmptyLEDMap();
|
||||
auto data = grabber->fft.getData()->leftChannel;
|
||||
auto val = 0.0;
|
||||
for (int i = 1; i < 4; ++i) {
|
||||
for (int i = 1; i < 3; ++i) {
|
||||
if (data[i] > val) {
|
||||
val = data[i];
|
||||
}
|
||||
|
|
|
@ -3,14 +3,15 @@
|
|||
#include <VUtils/Logging.h>
|
||||
#include <VUtils/Math.h>
|
||||
|
||||
#define MAX_DELTA 1212000
|
||||
#define MAX_DELTA 808000
|
||||
#define MAX_PEAK 170
|
||||
|
||||
namespace VIZ {
|
||||
RainbowLine::RainbowLine(AudioGrabber *pGrabber, Vulcan121 *vulcan) : VIZ(pGrabber, vulcan) {
|
||||
colours = new rgba[keyboardData.num_cols];
|
||||
for (int i = 0; i < keyboardData.num_cols; ++i) {
|
||||
double ratio = (double) i / keyboardData.num_cols;
|
||||
maxCols = keyboardData.num_cols * 5;
|
||||
colours = new rgba[maxCols];
|
||||
for (int i = 0; i < maxCols; ++i) {
|
||||
double ratio = (double) i / maxCols;
|
||||
colours[i] = Color::Generator::rgbFromRatio(ratio, 255);
|
||||
}
|
||||
}
|
||||
|
@ -28,12 +29,11 @@ namespace VIZ {
|
|||
}
|
||||
|
||||
void RainbowLine::onTick(float delta) {
|
||||
Vulcan121::fadeOutMap(data, tailFactor);
|
||||
deltaElapsed += delta;
|
||||
auto fftData = grabber->fft.getData()->leftChannel;
|
||||
auto val = 0.0;
|
||||
for (int i = 1; i < 4; ++i) {
|
||||
auto avg = fftData[i] * ratios[i];
|
||||
auto avg = fftData[i];
|
||||
if (avg > val)
|
||||
val = avg;
|
||||
}
|
||||
|
@ -46,19 +46,14 @@ namespace VIZ {
|
|||
currentColumn = 0;
|
||||
}
|
||||
}
|
||||
colours[currentColumn].a = val;
|
||||
auto column = keyboard->getColumn(currentColumn);
|
||||
for (int i = 0; i < column->count; ++i) {
|
||||
auto index = column->index[i];
|
||||
data[0].key[index] = colours[currentColumn];
|
||||
}
|
||||
moveLine(val, tailFactor);
|
||||
lastValue = avg;
|
||||
// we clean up the map self later! :)
|
||||
keyboard->sendLedMap(data, false);
|
||||
}
|
||||
|
||||
double RainbowLine::deltaMove(double val) {
|
||||
auto avg = (lastValue + val) * 0.5;
|
||||
auto avg = VUtils::Math::clamp((lastValue + val) * 0.5, 0, 200);
|
||||
if (avg > MAX_PEAK || avg > decayValue) {
|
||||
calcNextDelta(val < MAX_PEAK && firstUnder ? 160 : val);
|
||||
firstUnder = val > MAX_PEAK;
|
||||
|
@ -85,4 +80,15 @@ namespace VIZ {
|
|||
rRatio = (VUtils::Math::easeIn(rRatio) - 1.0) * -1;
|
||||
deltaNeeded = rRatio * MAX_DELTA;
|
||||
}
|
||||
|
||||
void RainbowLine::moveLine(double val, double d) {
|
||||
keyboard->fadeOutMapColumn(data, d);
|
||||
colours[currentColumn].a = val;
|
||||
int col = currentColumn % keyboardData.num_cols;
|
||||
auto column = keyboard->getColumn(col);
|
||||
for (int i = 0; i < column->count; ++i) {
|
||||
auto index = column->index[i];
|
||||
data[0].key[index] = colours[currentColumn];
|
||||
}
|
||||
}
|
||||
}
|
107
src/VulcanoLE/Scripts/RainbowMap.cpp
Normal file
107
src/VulcanoLE/Scripts/RainbowMap.cpp
Normal file
|
@ -0,0 +1,107 @@
|
|||
#include <VulcanoLE/Scripts/RainbowMap.h>
|
||||
#include <VulcanoLE/Colors/ColorHelper.h>
|
||||
#include <VUtils/Logging.h>
|
||||
#include <VUtils/Math.h>
|
||||
|
||||
#define MAX_DELTA 808000
|
||||
#define MAX_PEAK 170
|
||||
|
||||
namespace VIZ {
|
||||
RainbowMap::RainbowMap(AudioGrabber *pGrabber, Vulcan121 *vulcan) : VIZ(pGrabber, vulcan) {
|
||||
maxCols = keyboardData.num_cols * keyboardData.num_rows;
|
||||
colours = new rgba[maxCols];
|
||||
for (int i = 0; i < maxCols; ++i) {
|
||||
double ratio = (double) i / maxCols;
|
||||
colours[i] = Color::Generator::rgbFromRatio(ratio, 128);
|
||||
}
|
||||
}
|
||||
RainbowMap::~RainbowMap() {
|
||||
delete[] colours;
|
||||
delete data;
|
||||
}
|
||||
void RainbowMap::onSetup() {
|
||||
currentColumn = 0;
|
||||
Vulcan121::fadeOutMap(data, 0);
|
||||
keyboard->sendToLEDs({ 0, 0, 0, 0 });
|
||||
grabber->requestMode = AudioGrabber::ReqMode::FFT;
|
||||
usleep(100000);
|
||||
}
|
||||
|
||||
void RainbowMap::onTick(float delta) {
|
||||
deltaElapsed += delta;
|
||||
auto fftData = grabber->fft.getData()->leftChannel;
|
||||
auto val = 0.0;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
auto avg = fftData[i];
|
||||
if (avg > val)
|
||||
val = avg;
|
||||
}
|
||||
val = VUtils::Math::clamp(val, 0, 255);
|
||||
double avg = deltaMove(val);
|
||||
if (deltaElapsed >= deltaNeeded) {
|
||||
currentColumn++;
|
||||
if (currentColumn >= maxCols)
|
||||
currentColumn = 0;
|
||||
moveRainbow(avg);
|
||||
deltaElapsed -= deltaNeeded;
|
||||
}
|
||||
moveRainbow(avg);
|
||||
lastValue = avg;
|
||||
// we clean up the map self later! :)
|
||||
keyboard->sendLedMap(data, false);
|
||||
}
|
||||
|
||||
double RainbowMap::deltaMove(double val) {
|
||||
auto avg = VUtils::Math::clamp((lastValue + val) * 0.5, 0, 200);
|
||||
if (avg > MAX_PEAK || avg > decayValue) {
|
||||
calcNextDelta(val < MAX_PEAK && firstUnder ? 160 : val);
|
||||
firstUnder = val > MAX_PEAK;
|
||||
decayValue = val;
|
||||
}
|
||||
if (val > MAX_PEAK) {
|
||||
firstUnder = true;
|
||||
}
|
||||
if (avg < 10) {
|
||||
deltaNeeded = 1000000000;
|
||||
}
|
||||
if (decayValue >= 0)
|
||||
decayValue -= 10;
|
||||
return avg;
|
||||
}
|
||||
|
||||
const char *RainbowMap::name() {
|
||||
return m_name.c_str();
|
||||
}
|
||||
|
||||
void RainbowMap::calcNextDelta(double ratio) {
|
||||
double rRatio = ratio / MAX_PEAK;
|
||||
rRatio = VUtils::Math::clamp(rRatio, 0.05, 1.0);
|
||||
rRatio = (VUtils::Math::easeIn(rRatio) - 1.0) * -1;
|
||||
deltaNeeded = rRatio * MAX_DELTA;
|
||||
}
|
||||
|
||||
void RainbowMap::moveRainbow(double d) {
|
||||
int i = 0;
|
||||
int color = currentColumn;
|
||||
while (i < keyboardData.num_cols) {
|
||||
colours[color].a = (uint8_t) d;
|
||||
updateColumn(i, color);
|
||||
color++;
|
||||
i++;
|
||||
if (color > maxCols - 1)
|
||||
color = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void RainbowMap::updateColumn(int col, int colIndex) {
|
||||
auto column = keyboard->getColumn(col);
|
||||
if (!column) {
|
||||
ERR("Column: %d not found", col);
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < column->count; ++i) {
|
||||
auto index = column->index[i];
|
||||
data[0].key[index] = colours[colIndex];
|
||||
}
|
||||
}
|
||||
}
|
83
src/VulcanoLE/Scripts/Random.cpp
Normal file
83
src/VulcanoLE/Scripts/Random.cpp
Normal file
|
@ -0,0 +1,83 @@
|
|||
#include <VulcanoLE/Scripts/Random.h>
|
||||
#include <VUtils/Logging.h>
|
||||
#include <VUtils/Math.h>
|
||||
|
||||
namespace VIZ {
|
||||
Random::Random(AudioGrabber *pGrabber, Vulcan121 *vulcan) : VIZ(pGrabber, vulcan) {
|
||||
}
|
||||
void Random::onSetup() {
|
||||
keyboard->sendToLEDs({ 0, 0, 0, 0 });
|
||||
if (map == nullptr) {
|
||||
map = Vulcan121::createEmptyLEDMap();
|
||||
for (auto &color : map->key) {
|
||||
color.r = 255;
|
||||
color.b = 155;
|
||||
color.a = 0;
|
||||
}
|
||||
}
|
||||
grabber->requestMode = AudioGrabber::ReqMode::FFT;
|
||||
usleep(100000);
|
||||
}
|
||||
void Random::onTick(float delta) {
|
||||
deltaElapsed += delta;
|
||||
auto fftData = grabber->fft.getData()->leftChannel;
|
||||
auto val = 0.0;
|
||||
for (int i = 1; i < 4; ++i) {
|
||||
auto avg = fftData[i];
|
||||
if (avg > val)
|
||||
val = avg;
|
||||
}
|
||||
val = VUtils::Math::clamp(val, 1,255);
|
||||
if (deltaElapsed >= deltaNeeded) {
|
||||
deltaElapsed -= deltaNeeded;
|
||||
if (emptyTicks && ticks < 10) {
|
||||
ticks++;
|
||||
} else {
|
||||
ticks = 0;
|
||||
emptyTicks = false;
|
||||
auto *kRow = keyboard->getRow(row);
|
||||
columnCount += isReverse ? -1 : 1;
|
||||
if (!isReverse && columnCount > kRow->count) {
|
||||
row++;
|
||||
if (row >= keyboardData.num_rows) {
|
||||
emptyTicks = true;
|
||||
isReverse = true;
|
||||
row = keyboardData.num_rows-1;
|
||||
} else {
|
||||
columnCount = 0;
|
||||
}
|
||||
} else if (isReverse && columnCount < 0) {
|
||||
row--;
|
||||
if (row < 0) {
|
||||
emptyTicks = true;
|
||||
isReverse = false;
|
||||
row = 0;
|
||||
} else {
|
||||
columnCount = kRow->count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto &color : map->key)
|
||||
color.a = 0;
|
||||
for (int i = 0; i <= row; ++i) {
|
||||
auto *kRow = keyboard->getRow(i);
|
||||
if (kRow) {
|
||||
for (int j = 0; j < kRow->count; ++j) {
|
||||
auto intVal = (int16_t) val;
|
||||
if (i == row && j > columnCount)
|
||||
intVal = 0;
|
||||
map->key[kRow->index[j]].a = intVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
keyboard->sendLedMap(map, false);
|
||||
}
|
||||
const char *Random::name() {
|
||||
return "Random";
|
||||
}
|
||||
|
||||
Random::~Random() {
|
||||
delete map;
|
||||
}
|
||||
}
|
|
@ -14,8 +14,8 @@ namespace VIZ {
|
|||
// find largest bass ~43hz (44100 / FFT_SIZE) range
|
||||
auto val = 0.0;
|
||||
for (int i = 1; i < 4; ++i) {
|
||||
if (data[ i ] > val) {
|
||||
val = data[ i ];
|
||||
if (data[i] > val) {
|
||||
val = data[i];
|
||||
}
|
||||
}
|
||||
double newVal = (val + lastVal) / 2.0;
|
||||
|
@ -27,7 +27,7 @@ namespace VIZ {
|
|||
colors[1].a = newVal;
|
||||
colors[2].a = newVal;
|
||||
for (int key = 0; key < keyboardData.num_keys; ++key) {
|
||||
map[ 0 ].key[ key ] = colors[ colorInd ];
|
||||
map[0].key[key] = colors[colorInd];
|
||||
x++;
|
||||
if (x > split) {
|
||||
colorInd++;
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
#include <VulcanoLE/Scripts/PoliceLike.h>
|
||||
#include <VUtils/Logging.h>
|
||||
#include <VulcanoLE/Scripts/RainbowLine.h>
|
||||
#include <VulcanoLE/Scripts/Random.h>
|
||||
#include <VulcanoLE/Scripts/RainbowMap.h>
|
||||
|
||||
namespace VIZ {
|
||||
void VisPlugins::init(HIDHelper *hidHelper, AudioGrabber *audioGrabber) {
|
||||
|
@ -13,10 +15,13 @@ namespace VIZ {
|
|||
viz[1] = new Loudness(grabber, keyboard);
|
||||
viz[2] = new PoliceLike(grabber, keyboard);
|
||||
viz[3] = new RainbowLine(grabber, keyboard);
|
||||
viz[4] = new Random(grabber, keyboard);
|
||||
viz[5] = new RainbowMap(grabber, keyboard);
|
||||
currentVis = viz[mode];
|
||||
}
|
||||
|
||||
void VisPlugins::onStartup() {
|
||||
std::lock_guard<std::mutex> lockGuard(guard);
|
||||
if (!keyboard->sendInitSequence()) {
|
||||
ERR("FAILED TO INIT KEYBOARD")
|
||||
exit(1);
|
||||
|
@ -26,14 +31,25 @@ namespace VIZ {
|
|||
}
|
||||
|
||||
void VisPlugins::onTick() {
|
||||
std::lock_guard<std::mutex> lockGuard(guard);
|
||||
auto stop = std::chrono::high_resolution_clock::now();
|
||||
auto delta = std::chrono::duration_cast<micro>(stop - start).count();
|
||||
currentVis->onTick(delta);
|
||||
usleep(1000);
|
||||
frames++;
|
||||
#ifdef DEBUG
|
||||
auto fps = std::chrono::duration_cast<ms>(stop - frameStart).count();
|
||||
if (fps > 1000) {
|
||||
frameStart = stop;
|
||||
DBG("FPS: %.2f, time-needed: %d ms", double(frames) / (int) fps * 1000.0, (int) fps)
|
||||
frames = 0;
|
||||
}
|
||||
#endif
|
||||
// DBG("Time Needed %f ms", delta / 1000)
|
||||
start = stop;
|
||||
}
|
||||
|
||||
void VisPlugins::onShutdown() {
|
||||
std::lock_guard<std::mutex> lockGuard(guard);
|
||||
int16_t r = env->getAsInt("shutdown_color_red", 0);
|
||||
int16_t g = env->getAsInt("shutdown_color_green", 0);
|
||||
int16_t b = env->getAsInt("shutdown_color_blue", 150);
|
||||
|
@ -50,6 +66,7 @@ namespace VIZ {
|
|||
}
|
||||
|
||||
void VisPlugins::setCurrentMode(int m) {
|
||||
std::lock_guard<std::mutex> lockGuard(guard);
|
||||
if (m < 1 || m > VIZSIZE) {
|
||||
ERR("Mode Setting Failed >> Mode is not in the available range 1 - %d", VIZSIZE)
|
||||
return;
|
||||
|
|
Loading…
Reference in a new issue