#include #include #include #include #include #include #include "imgui.h" #include "imgui_impl_glfw.h" #include "imgui_impl_opengl3.h" #include #include #include #define MINIAUDIO_IMPLEMENTATION #include "miniaudio.h" #include "tinyfiledialogs.h" #include 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(); void renderTestImGui(); // ui timeline stuff std::vector> markers; // Time, R, G, B float timeline_position = 0.0f; bool is_playing = false; float playback_start_time = 0.0f; float current_time = 0.0f; float timeline_duration = 10.0f; // 10 seconds by default float bpm = 120.0f; // Default BPM float zoom_level = 1.0f; // Zoom level float pan_offset = 0.0f; // Pan offset // Calculate time per beat based on BPM float beat_interval = 60.0f / bpm; // audio stuff ma_sound sound; ma_engine engine; bool audioLoaded = false; std::vector audioSamples; bool audioDataLoaded = false; std::vector downsampledAudioSamples; const int num_channels = 512; unsigned char buffer[num_channels] = { 0 }; GLFWwindow* initOpenGL(); HANDLE hSerial; std::atomic running(true); void dmxThreadFunc() { while (running) { // 25ms refresh rate sendDMXData(hSerial, buffer, num_channels); std::this_thread::sleep_for(std::chrono::milliseconds(25)); } } int main() { hSerial = CreateFileA("\\\\.\\COM3", GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if (hSerial == INVALID_HANDLE_VALUE) { std::cerr << "Error opening COM3 port" << std::endl; return 1; } DCB dcbSerialParams = { 0 }; dcbSerialParams.DCBlength = sizeof(dcbSerialParams); if (!GetCommState(hSerial, &dcbSerialParams)) { std::cerr << "Error getting COM3 state" << std::endl; CloseHandle(hSerial); return 1; } dcbSerialParams.BaudRate = 250000; dcbSerialParams.ByteSize = 8; dcbSerialParams.StopBits = TWOSTOPBITS; // <-- don't forget about this dcbSerialParams.Parity = NOPARITY; if (!SetCommState(hSerial, &dcbSerialParams)) { std::cerr << "Error setting COM3 state" << std::endl; CloseHandle(hSerial); return 1; } COMMTIMEOUTS timeouts = { 0 }; timeouts.ReadIntervalTimeout = 50; timeouts.ReadTotalTimeoutConstant = 50; timeouts.ReadTotalTimeoutMultiplier = 10; timeouts.WriteTotalTimeoutConstant = 50; timeouts.WriteTotalTimeoutMultiplier = 10; if (!SetCommTimeouts(hSerial, &timeouts)) { std::cerr << "Error setting COM3 timeouts" << std::endl; CloseHandle(hSerial); return 1; } if (ma_engine_init(NULL, &engine) != MA_SUCCESS) { std::cerr << "Failed to initialize audio engine." << std::endl; return -1; } GLFWwindow* window = initOpenGL(); if (!window) { CloseHandle(hSerial); return 1; } setupImGui(window); std::thread dmxThread(dmxThreadFunc); while (!glfwWindowShouldClose(window)) { glfwPollEvents(); ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); // renderImGui(); // renderImGuiBpm(); renderTestImGui(); ImGui::Render(); int display_w, display_h; glfwGetFramebufferSize(window, &display_w, &display_h); glViewport(0, 0, display_w, display_h); glClearColor(0.45f, 0.55f, 0.60f, 1.00f); glClear(GL_COLOR_BUFFER_BIT); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); glfwSwapBuffers(window); } running = false; dmxThread.join(); ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplGlfw_Shutdown(); ImGui::DestroyContext(); glfwDestroyWindow(window); glfwTerminate(); CloseHandle(hSerial); ma_engine_uninit(&engine); return 0; } void generateRandomColors(unsigned char& r, unsigned char& g, unsigned char& b) { r = rand() % 256; g = rand() % 256; b = rand() % 256; } void sendDMXData(HANDLE hSerial, unsigned char* data, int length) { // break condition EscapeCommFunction(hSerial, SETBREAK); std::this_thread::sleep_for(std::chrono::milliseconds(1)); EscapeCommFunction(hSerial, CLRBREAK); std::this_thread::sleep_for(std::chrono::milliseconds(1)); // this is the mab DWORD bytesWritten; unsigned char startCode = 0; WriteFile(hSerial, &startCode, 1, &bytesWritten, NULL); WriteFile(hSerial, data, length, &bytesWritten, NULL); } // Function to export markers to a JSON file void exportMarkersToFile(const std::string& filename) { nlohmann::json j; for (const auto& marker : markers) { j.push_back({ {"time", std::get<0>(marker)}, {"r", std::get<1>(marker)}, {"g", std::get<2>(marker)}, {"b", std::get<3>(marker)} }); } std::ofstream file(filename); if (file.is_open()) { file << j.dump(4); // Pretty print with indentation of 4 spaces file.close(); } else { std::cerr << "Could not open file for writing: " << filename << std::endl; } } void downsampleAudioData(const std::vector& inputSamples, int targetSampleCount) { downsampledAudioSamples.clear(); int inputSampleCount = static_cast(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; std::ifstream file(filename); if (file.is_open()) { file >> j; file.close(); markers.clear(); for (const auto& element : j) { markers.emplace_back( element["time"].get(), element["r"].get(), element["g"].get(), element["b"].get() ); } } else { std::cerr << "Could not open file for reading: " << filename << std::endl; } } void normalizeAudioData(std::vector& 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); } 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(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); if (filename) { return std::string(filename); } return ""; } GLFWwindow* initOpenGL() { if (!glfwInit()) { std::cerr << "Failed to initialize GLFW" << std::endl; return nullptr; } GLFWwindow* window = glfwCreateWindow(1280, 720, "DMX Controller", NULL, NULL); if (!window) { std::cerr << "Failed to create GLFW window" << std::endl; glfwTerminate(); return nullptr; } glfwMakeContextCurrent(window); glfwSwapInterval(1); // I think this is vsync?? if (glewInit() != GLEW_OK) { std::cerr << "Failed to initialize GLEW" << std::endl; glfwDestroyWindow(window); glfwTerminate(); return nullptr; } return window; } void setupImGui(GLFWwindow* window) { IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); (void)io; ImGui::StyleColorsDark(); ImGui_ImplGlfw_InitForOpenGL(window, true); ImGui_ImplOpenGL3_Init("#version 130"); } void renderImGui() { // Temporary variables to hold the slider values int channel1 = buffer[0]; int channel2 = buffer[1]; int channel3 = buffer[2]; // Create a window ImGui::Begin("DMX Controller"); // Create buttons to export and load markers if (ImGui::Button("Export Lightshow")) { const char* filterPatterns[2] = { "*.ls", "*.json" }; const char* saveFileName = tinyfd_saveFileDialog( "Save As", // Dialog title "lightshow.ls", // Default file name 2, // Number of filter patterns filterPatterns, // Filter patterns NULL // Single filter description (optional) ); if (saveFileName) { std::cout << "File saved as: " << saveFileName << std::endl; exportMarkersToFile(saveFileName); } else { std::cout << "Save operation was canceled." << std::endl; } } ImGui::SameLine(); if (ImGui::Button("Load Lightshow")) { const char* filterPatterns[2] = { "*.ls", "*.json" }; std::string filename = tinyfd_openFileDialog("Select a file", "", 2, filterPatterns, NULL, 0); if (!filename.empty()) { loadMarkersFromFile(filename); } } if (ImGui::Button("Load Audio File")) { std::string filename = OpenAudioFileDialog(); if (!filename.empty()) { loadAndPlayAudio(filename); } } // Create sliders for the first three DMX channels ImGui::SliderInt("Channel 1", &channel1, 0, 255); ImGui::SliderInt("Channel 2", &channel2, 0, 255); ImGui::SliderInt("Channel 3", &channel3, 0, 255); // Assign the slider values back to the DMX buffer buffer[0] = static_cast(channel1); buffer[1] = static_cast(channel2); buffer[2] = static_cast(channel3); // Allow user to set the total duration of the timeline and BPM ImGui::SliderFloat("Timeline Duration (seconds)", &timeline_duration, 1.0f, 300.0f); 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; } // Play/Pause button if (ImGui::Button(is_playing ? "Pause" : "Play")) { is_playing = !is_playing; if (is_playing) { playback_start_time = static_cast(glfwGetTime()) - timeline_position; if (audioLoaded) { ma_uint64 frameCount; ma_sound_get_length_in_pcm_frames(&sound, &frameCount); // Calculate the frame to seek to based on the timeline position ma_uint64 targetFrame = static_cast((timeline_position / timeline_duration) * frameCount); ma_sound_seek_to_pcm_frame(&sound, targetFrame); ma_sound_start(&sound); } } else { if (audioLoaded) { ma_sound_stop(&sound); } } } // Stop Music button ImGui::SameLine(); if (ImGui::Button("Stop Music")) { if (audioLoaded) { ma_sound_stop(&sound); } is_playing = false; timeline_position = 0.0f; } // Time Slider (Timeline Position) ImGui::SliderFloat("Timeline Position", &timeline_position, 0.0f, timeline_duration, "%.2f sec"); // Custom timeline rendering with grid lines ImGui::Text("Timeline"); ImGui::PushID("CustomTimeline"); 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(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(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; if (x_position >= cursor_pos.x && x_position <= cursor_pos.x + timeline_width) { // Draw major beat line if (i % 4 == 0) { draw_list->AddLine(ImVec2(x_position, cursor_pos.y), ImVec2(x_position, cursor_pos.y + 100), IM_COL32(255, 0, 0, 255), 2.0f); } else { // Draw minor beat line draw_list->AddLine(ImVec2(x_position, cursor_pos.y), ImVec2(x_position, cursor_pos.y + 100), IM_COL32(255, 255, 255, 255), 1.0f); } } } // Draw the current timeline position marker float position_marker = (timeline_position * zoom_level - pan_offset) / timeline_duration * timeline_width; draw_list->AddLine(ImVec2(cursor_pos.x + position_marker, cursor_pos.y), ImVec2(cursor_pos.x + position_marker, cursor_pos.y + 100), IM_COL32(0, 0, 255, 255), 2.0f); // Allow adding markers with snapping to grid if (ImGui::Button("Add Marker")) { float snap_position = std::round(timeline_position / beat_interval) * beat_interval; 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; float x_position = cursor_pos.x + (marker_position / timeline_duration) * timeline_width; if (x_position >= cursor_pos.x && x_position <= cursor_pos.x + timeline_width) { draw_list->AddRectFilled(ImVec2(x_position - 2.0f, cursor_pos.y), ImVec2(x_position + 2.0f, cursor_pos.y + 100), IM_COL32(0, 255, 0, 255)); } } ImGui::EndChild(); ImGui::PopID(); // Playback logic: Update timeline position based on playback time if (is_playing) { current_time = static_cast(glfwGetTime()) - playback_start_time; timeline_position = current_time; for (const auto& marker : markers) { float marker_time = std::get<0>(marker) * timeline_duration; if (current_time >= marker_time && current_time < marker_time + 0.025f) { buffer[0] = std::get<1>(marker); buffer[1] = std::get<2>(marker); buffer[2] = std::get<3>(marker); } } if (timeline_position >= timeline_duration) { is_playing = false; timeline_position = timeline_duration; if (audioLoaded) { ma_sound_stop(&sound); } } } // Display added markers with their RGB values and time for (const auto& marker : markers) { float marker_time = std::get<0>(marker) * timeline_duration; ImGui::Text("Marker at %.2f sec: R=%d, G=%d, B=%d", marker_time, std::get<1>(marker), std::get<2>(marker), std::get<3>(marker)); } // Place Zoom and Pan sliders below the timeline to avoid overlap ImGui::SliderFloat("Zoom", &zoom_level, 0.1f, 10.0f); 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 bool has_applied_offset = false; static float lastBeatTime = 0.0f; static float pauseTime = 0.0f; // Store the time when BPM is paused if (ImGui::Button(is_playing_bpm ? "Stop" : "Start")) { if (is_playing_bpm) { // Stop BPM playback pauseTime = timeline_position; is_playing_bpm = false; } else { // Start BPM playback if (!is_playing && audioLoaded) { if (!has_applied_offset) { playback_start_time = static_cast(glfwGetTime()) - timeline_position - offset_seconds; has_applied_offset = true; } else { playback_start_time = static_cast(glfwGetTime()) - timeline_position; } ma_uint64 frameCount; ma_sound_get_length_in_pcm_frames(&sound, &frameCount); float playback_time = timeline_position + offset_seconds; ma_uint64 targetFrame = static_cast((playback_time / timeline_duration) * frameCount); ma_sound_seek_to_pcm_frame(&sound, targetFrame); ma_sound_start(&sound); is_playing = true; } if (!is_playing_bpm) { // Adjust lastBeatTime based on the pause time lastBeatTime += (timeline_position - pauseTime); } is_playing_bpm = true; } } // Non-blocking BPM-based Random Color Changes if (is_playing_bpm && audioLoaded) { float currentTime = timeline_position; float timePerBeat = 60.0f / bpm; if (currentTime - lastBeatTime >= timePerBeat) { int beatsSkipped = static_cast((currentTime - lastBeatTime) / timePerBeat); lastBeatTime += beatsSkipped * timePerBeat; unsigned char r = rand() % 256; unsigned char g = rand() % 256; unsigned char b = rand() % 256; buffer[0] = r; buffer[1] = g; buffer[2] = b; } } ImGui::End(); } void renderTestImGui() { ImGui::Begin("Timeline"); // Time Slider (Timeline Position) ImGui::SliderFloat("Timeline Position", &timeline_position, 0.0f, timeline_duration, "%.2f sec"); // Custom timeline rendering with grid lines ImGui::Text("Timeline"); ImGui::PushID("CustomTimeline"); 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(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(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; if (x_position >= cursor_pos.x && x_position <= cursor_pos.x + timeline_width) { // Draw major beat line if (i % 4 == 0) { draw_list->AddLine(ImVec2(x_position, cursor_pos.y), ImVec2(x_position, cursor_pos.y + 100), IM_COL32(255, 0, 0, 255), 2.0f); } else { // Draw minor beat line draw_list->AddLine(ImVec2(x_position, cursor_pos.y), ImVec2(x_position, cursor_pos.y + 100), IM_COL32(255, 255, 255, 255), 1.0f); } } } // Draw the current timeline position marker float position_marker = (timeline_position * zoom_level - pan_offset) / timeline_duration * timeline_width; draw_list->AddLine(ImVec2(cursor_pos.x + position_marker, cursor_pos.y), ImVec2(cursor_pos.x + position_marker, cursor_pos.y + 100), IM_COL32(0, 0, 255, 255), 2.0f); // Allow adding markers with snapping to grid if (ImGui::Button("Add Marker")) { float snap_position = std::round(timeline_position / beat_interval) * beat_interval; 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; float x_position = cursor_pos.x + (marker_position / timeline_duration) * timeline_width; if (x_position >= cursor_pos.x && x_position <= cursor_pos.x + timeline_width) { draw_list->AddRectFilled(ImVec2(x_position - 2.0f, cursor_pos.y), ImVec2(x_position + 2.0f, cursor_pos.y + 100), IM_COL32(0, 255, 0, 255)); } } ImGui::EndChild(); ImGui::PopID(); // Playback logic: Update timeline position based on playback time if (is_playing) { current_time = static_cast(glfwGetTime()) - playback_start_time; timeline_position = current_time; for (const auto& marker : markers) { float marker_time = std::get<0>(marker) * timeline_duration; if (current_time >= marker_time && current_time < marker_time + 0.025f) { buffer[0] = std::get<1>(marker); buffer[1] = std::get<2>(marker); buffer[2] = std::get<3>(marker); } } if (timeline_position >= timeline_duration) { is_playing = false; timeline_position = timeline_duration; if (audioLoaded) { ma_sound_stop(&sound); } } } // Display added markers with their RGB values and time for (const auto& marker : markers) { float marker_time = std::get<0>(marker) * timeline_duration; ImGui::Text("Marker at %.2f sec: R=%d, G=%d, B=%d", marker_time, std::get<1>(marker), std::get<2>(marker), std::get<3>(marker)); } ImGui::End(); }