Add random colors to follow bpm
This commit is contained in:
parent
419ee8fd7c
commit
6befaf5eea
204
main.cpp
204
main.cpp
@ -13,12 +13,14 @@
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "miniaudio.h"
|
||||
#include "tinyfiledialogs.h"
|
||||
#include <algorithm>
|
||||
|
||||
|
||||
void generateRandomColors(unsigned char& r, unsigned char& g, unsigned char& b);
|
||||
void sendDMXData(HANDLE hSerial, unsigned char* data, int length);
|
||||
void setupImGui(GLFWwindow* window);
|
||||
void renderImGui();
|
||||
void renderImGuiBpm();
|
||||
|
||||
|
||||
// ui timeline stuff
|
||||
@ -37,6 +39,10 @@ ma_sound sound;
|
||||
ma_engine engine;
|
||||
bool audioLoaded = false;
|
||||
|
||||
std::vector<float> audioSamples;
|
||||
bool audioDataLoaded = false;
|
||||
|
||||
std::vector<float> downsampledAudioSamples;
|
||||
|
||||
const int num_channels = 512;
|
||||
unsigned char buffer[num_channels] = { 0 };
|
||||
@ -115,8 +121,10 @@ int main() {
|
||||
ImGui::NewFrame();
|
||||
|
||||
renderImGui();
|
||||
renderImGuiBpm();
|
||||
|
||||
ImGui::Render();
|
||||
|
||||
int display_w, display_h;
|
||||
glfwGetFramebufferSize(window, &display_w, &display_h);
|
||||
glViewport(0, 0, display_w, display_h);
|
||||
@ -184,6 +192,32 @@ void exportMarkersToFile(const std::string& filename) {
|
||||
}
|
||||
}
|
||||
|
||||
void downsampleAudioData(const std::vector<float>& inputSamples, int targetSampleCount) {
|
||||
downsampledAudioSamples.clear();
|
||||
int inputSampleCount = static_cast<int>(inputSamples.size());
|
||||
int samplesPerDownsample = inputSampleCount / targetSampleCount;
|
||||
|
||||
for (int i = 0; i < targetSampleCount; ++i) {
|
||||
float maxSample = -1.0f;
|
||||
float minSample = 1.0f;
|
||||
int startIdx = i * samplesPerDownsample;
|
||||
int endIdx = (std::min)(startIdx + samplesPerDownsample, inputSampleCount);// line 201
|
||||
|
||||
for (int j = startIdx; j < endIdx; ++j) {
|
||||
if (inputSamples[j] > maxSample) {
|
||||
maxSample = inputSamples[j];
|
||||
}
|
||||
if (inputSamples[j] < minSample) {
|
||||
minSample = inputSamples[j];
|
||||
}
|
||||
}
|
||||
|
||||
// Store both max and min to preserve the waveform's shape
|
||||
downsampledAudioSamples.push_back(maxSample);
|
||||
downsampledAudioSamples.push_back(minSample);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to load markers from a JSON file
|
||||
void loadMarkersFromFile(const std::string& filename) {
|
||||
nlohmann::json j;
|
||||
@ -208,6 +242,20 @@ void loadMarkersFromFile(const std::string& filename) {
|
||||
}
|
||||
}
|
||||
|
||||
void normalizeAudioData(std::vector<float>& data) {
|
||||
float maxAmplitude = 0.0f;
|
||||
for (float sample : data) {
|
||||
if (fabs(sample) > maxAmplitude) {
|
||||
maxAmplitude = fabs(sample);
|
||||
}
|
||||
}
|
||||
if (maxAmplitude > 0.0f) {
|
||||
for (float& sample : data) {
|
||||
sample /= maxAmplitude;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void loadAndPlayAudio(const std::string& filename) {
|
||||
if (audioLoaded) {
|
||||
ma_sound_uninit(&sound);
|
||||
@ -215,13 +263,41 @@ void loadAndPlayAudio(const std::string& filename) {
|
||||
|
||||
if (ma_sound_init_from_file(&engine, filename.c_str(), MA_SOUND_FLAG_STREAM, NULL, NULL, &sound) == MA_SUCCESS) {
|
||||
audioLoaded = true;
|
||||
|
||||
ma_uint64 frameCount;
|
||||
ma_sound_get_length_in_pcm_frames(&sound, &frameCount);
|
||||
|
||||
ma_format format;
|
||||
ma_uint32 channels;
|
||||
ma_uint32 sampleRate;
|
||||
ma_channel channelMap[MA_MAX_CHANNELS];
|
||||
size_t channelMapCap = MA_MAX_CHANNELS;
|
||||
|
||||
// Correct function call with all required arguments
|
||||
ma_data_source_get_data_format(sound.pDataSource, &format, &channels, &sampleRate, channelMap, channelMapCap);
|
||||
|
||||
float audioDuration = frameCount / static_cast<float>(sampleRate);
|
||||
timeline_duration = audioDuration; // Set the timeline duration to the audio length
|
||||
|
||||
audioSamples.resize(frameCount);
|
||||
ma_data_source_seek_to_pcm_frame(sound.pDataSource, 0);
|
||||
ma_data_source_read_pcm_frames(sound.pDataSource, audioSamples.data(), frameCount, NULL);
|
||||
|
||||
audioDataLoaded = true;
|
||||
|
||||
// Normalize and downsample audio data for visualization
|
||||
normalizeAudioData(audioSamples);
|
||||
downsampleAudioData(audioSamples, 20000); // Target around 2000 points for better resolution
|
||||
|
||||
ma_sound_start(&sound);
|
||||
}
|
||||
else {
|
||||
std::cerr << "Failed to load audio file: " << filename << std::endl;
|
||||
audioDataLoaded = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::string OpenAudioFileDialog() {
|
||||
const char* filterPatterns[2] = { "*.mp3", "*.wav" };
|
||||
const char* filename = tinyfd_openFileDialog("Select an audio file", "", 2, filterPatterns, NULL, 0);
|
||||
@ -257,6 +333,7 @@ GLFWwindow* initOpenGL() {
|
||||
return window;
|
||||
}
|
||||
|
||||
|
||||
void setupImGui(GLFWwindow* window) {
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
@ -379,13 +456,38 @@ void renderImGui() {
|
||||
// Custom timeline rendering with grid lines
|
||||
ImGui::Text("Timeline");
|
||||
ImGui::PushID("CustomTimeline");
|
||||
ImGui::BeginChild("##Timeline", ImVec2(ImGui::GetContentRegionAvail().x, 100), true);
|
||||
ImGui::BeginChild("##Timeline", ImVec2(ImGui::GetContentRegionAvail().x, 150), true);
|
||||
|
||||
// Draw grid lines for beats
|
||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||
ImVec2 cursor_pos = ImGui::GetCursorScreenPos();
|
||||
float timeline_width = ImGui::GetContentRegionAvail().x;
|
||||
|
||||
// Draw the audio waveform if loaded
|
||||
if (audioDataLoaded && !downsampledAudioSamples.empty()) {
|
||||
float timelineHeight = 100.0f; // Total height of the timeline
|
||||
float waveformHeight = timelineHeight / 2.0f; // Waveform takes half the height of the timeline
|
||||
float yOffset = cursor_pos.y + timelineHeight / 2.0f; // Center the waveform vertically
|
||||
|
||||
int sampleCount = static_cast<int>(downsampledAudioSamples.size()) / 2;
|
||||
|
||||
// Adjust the scaling to fit the timeline duration
|
||||
float pixelsPerSample = (timeline_width * zoom_level) / timeline_duration;
|
||||
|
||||
for (int i = 0; i < sampleCount - 1; ++i) {
|
||||
float x1 = cursor_pos.x + i * pixelsPerSample;
|
||||
float x2 = cursor_pos.x + (i + 1) * pixelsPerSample;
|
||||
|
||||
float y1_max = yOffset - (downsampledAudioSamples[i * 2] * waveformHeight);
|
||||
float y1_min = yOffset - (downsampledAudioSamples[i * 2 + 1] * waveformHeight);
|
||||
float y2_max = yOffset - (downsampledAudioSamples[(i + 1) * 2] * waveformHeight);
|
||||
float y2_min = yOffset - (downsampledAudioSamples[(i + 1) * 2 + 1] * waveformHeight);
|
||||
|
||||
draw_list->AddLine(ImVec2(x1, y1_max), ImVec2(x2, y2_max), IM_COL32(0, 255, 0, 255), 1.0f);
|
||||
draw_list->AddLine(ImVec2(x1, y1_min), ImVec2(x2, y2_min), IM_COL32(0, 255, 0, 255), 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i <= static_cast<int>(timeline_duration / beat_interval); ++i) {
|
||||
float beat_position = i * beat_interval * zoom_level - pan_offset;
|
||||
float x_position = cursor_pos.x + (beat_position / timeline_duration) * timeline_width;
|
||||
@ -412,6 +514,20 @@ void renderImGui() {
|
||||
markers.emplace_back(snap_position / timeline_duration, buffer[0], buffer[1], buffer[2]);
|
||||
}
|
||||
|
||||
// Add Random Marker button
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Add Random Marker")) {
|
||||
float snap_position = std::round(timeline_position / beat_interval) * beat_interval;
|
||||
|
||||
// Generate random colors
|
||||
unsigned char r = rand() % 256;
|
||||
unsigned char g = rand() % 256;
|
||||
unsigned char b = rand() % 256;
|
||||
|
||||
// Add the marker with random colors
|
||||
markers.emplace_back(snap_position / timeline_duration, r, g, b);
|
||||
}
|
||||
|
||||
// Draw markers on the timeline
|
||||
for (const auto& marker : markers) {
|
||||
float marker_position = std::get<0>(marker) * timeline_duration * zoom_level - pan_offset;
|
||||
@ -464,4 +580,88 @@ void renderImGui() {
|
||||
ImGui::SliderFloat("Pan", &pan_offset, 0.0f, timeline_duration * zoom_level);
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
|
||||
void renderImGuiBpm() {
|
||||
|
||||
// New BPM Control Window
|
||||
ImGui::Begin("BPM Controller");
|
||||
|
||||
// BPM Slider/Input
|
||||
ImGui::SliderFloat("BPM", &bpm, 30.0f, 240.0f);
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(50);
|
||||
if (ImGui::InputFloat("##BPMInput", &bpm, 0.0f, 0.0f, "%.1f")) {
|
||||
if (bpm < 30.0f) bpm = 30.0f;
|
||||
if (bpm > 240.0f) bpm = 240.0f;
|
||||
}
|
||||
|
||||
// Offset Slider in milliseconds
|
||||
static float offset_ms = 0.0f;
|
||||
ImGui::SliderFloat("Offset (ms)", &offset_ms, -5000.0f, 5000.0f, "%.0f ms");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(70);
|
||||
if (ImGui::InputFloat("##OffsetInput", &offset_ms, 0.0f, 0.0f, "%.0f")) {
|
||||
if (offset_ms < -5000.0f) offset_ms = -5000.0f;
|
||||
if (offset_ms > 5000.0f) offset_ms = 5000.0f;
|
||||
}
|
||||
|
||||
// Convert offset from milliseconds to seconds for internal use
|
||||
float offset_seconds = offset_ms / 1000.0f;
|
||||
|
||||
// Start/Stop button
|
||||
static bool is_playing_bpm = false;
|
||||
static float lastBeatTime = 0.0f;
|
||||
if (ImGui::Button(is_playing_bpm ? "Stop" : "Start")) {
|
||||
is_playing_bpm = !is_playing_bpm;
|
||||
if (is_playing_bpm) {
|
||||
// Start playing the audio if not already playing
|
||||
if (!is_playing && audioLoaded) {
|
||||
// Apply offset to playback start time
|
||||
playback_start_time = static_cast<float>(glfwGetTime()) - timeline_position - offset_seconds;
|
||||
|
||||
ma_uint64 frameCount;
|
||||
ma_sound_get_length_in_pcm_frames(&sound, &frameCount);
|
||||
// Calculate the frame to seek to based on the timeline position and offset
|
||||
float playback_time = timeline_position + offset_seconds;
|
||||
ma_uint64 targetFrame = static_cast<ma_uint64>((playback_time / timeline_duration) * frameCount);
|
||||
ma_sound_seek_to_pcm_frame(&sound, targetFrame);
|
||||
ma_sound_start(&sound);
|
||||
is_playing = true;
|
||||
}
|
||||
// Initialize the beat timer with offset
|
||||
lastBeatTime = timeline_position; // Start the beat timer from the timeline position
|
||||
}
|
||||
else {
|
||||
if (audioLoaded) {
|
||||
ma_sound_stop(&sound);
|
||||
is_playing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Non-blocking BPM-based Random Color Changes
|
||||
if (is_playing_bpm && audioLoaded) {
|
||||
float currentTime = timeline_position + offset_seconds; // Apply offset to current time
|
||||
float timePerBeat = 60.0f / bpm;
|
||||
|
||||
// Only update the color when the current time exceeds the next beat time
|
||||
if (currentTime - lastBeatTime >= timePerBeat) {
|
||||
// Calculate how many beats we have skipped and adjust `lastBeatTime`
|
||||
int beatsSkipped = static_cast<int>((currentTime - lastBeatTime) / timePerBeat);
|
||||
lastBeatTime += beatsSkipped * timePerBeat;
|
||||
|
||||
// Generate new random colors every beat
|
||||
unsigned char r = rand() % 256;
|
||||
unsigned char g = rand() % 256;
|
||||
unsigned char b = rand() % 256;
|
||||
|
||||
// Set the DMX buffer with the random colors
|
||||
buffer[0] = r;
|
||||
buffer[1] = g;
|
||||
buffer[2] = b;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user