This commit is contained in:
Maurice Grönwoldt 2021-05-02 17:25:03 +02:00
parent d0e8eb0d5e
commit 4152ee0413
33 changed files with 27356 additions and 233 deletions

View File

@ -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
View 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)

View File

@ -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);
};
}

View File

@ -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);
};
}

View File

@ -0,0 +1,12 @@
#pragma once
#include <string>
struct I2CDevice {
std::string name;
};
class I2C {
public:
int readDevices();
};

View File

@ -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;
};

View File

@ -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;
};

View 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);
};

View File

@ -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;

View File

@ -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;
};

View File

@ -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

View File

@ -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;

View File

@ -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;
};
}

View 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);
};
}

View 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;
};
}

View File

@ -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 }
};
};
}

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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"(
[===============================================]

View File

@ -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;
}
}

View File

@ -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
View 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;
}

View File

@ -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;
}
}

View File

@ -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;
}

View 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;
}

View File

@ -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);

View File

@ -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;
}
}
}

View File

@ -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];
}

View File

@ -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];
}
}
}

View 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];
}
}
}

View 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;
}
}

View File

@ -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++;

View File

@ -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;