Add random colors to follow bpm

This commit is contained in:
illyum 2024-08-09 02:14:16 -06:00
parent 419ee8fd7c
commit 6befaf5eea

204
main.cpp
View File

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