Compare commits
10 Commits
e78c0fa96a
...
332d3e3b33
Author | SHA1 | Date | |
---|---|---|---|
![]() |
332d3e3b33 | ||
![]() |
9b738c359d | ||
![]() |
b3837e21d5 | ||
![]() |
6befaf5eea | ||
![]() |
419ee8fd7c | ||
![]() |
e3228fa03d | ||
![]() |
9ed675e3f8 | ||
![]() |
7333fdc401 | ||
![]() |
fa9216a110 | ||
![]() |
1c4c092967 |
@ -15,7 +15,7 @@ set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
||||
set(FETCHCONTENT_QUIET OFF)
|
||||
|
||||
add_executable(${PROJECT_NAME} main.cpp)
|
||||
add_executable(${PROJECT_NAME} main.cpp tinyfiledialogs.c timeline.cpp)
|
||||
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
@ -45,11 +45,21 @@ FetchContent_Declare(
|
||||
)
|
||||
FetchContent_MakeAvailable(glew)
|
||||
|
||||
FetchContent_Declare(
|
||||
json
|
||||
GIT_REPOSITORY https://github.com/nlohmann/json.git
|
||||
GIT_TAG master
|
||||
GIT_SHALLOW ON
|
||||
GIT_PROGRESS ON
|
||||
)
|
||||
FetchContent_MakeAvailable(json)
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE
|
||||
${imgui_SOURCE_DIR}
|
||||
${imgui_SOURCE_DIR}/backends
|
||||
${glfw_SOURCE_DIR}/include
|
||||
${glew_SOURCE_DIR}/include
|
||||
${json_SOURCE_DIR}/include
|
||||
)
|
||||
|
||||
target_sources(${PROJECT_NAME} PRIVATE
|
||||
|
79
ImSequencer.h
Normal file
79
ImSequencer.h
Normal file
@ -0,0 +1,79 @@
|
||||
// https://github.com/CedricGuillemet/ImGuizmo
|
||||
// v 1.89 WIP
|
||||
//
|
||||
// The MIT License(MIT)
|
||||
//
|
||||
// Copyright(c) 2021 Cedric Guillemet
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files(the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions :
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
struct ImDrawList;
|
||||
struct ImRect;
|
||||
namespace ImSequencer
|
||||
{
|
||||
enum SEQUENCER_OPTIONS
|
||||
{
|
||||
SEQUENCER_EDIT_NONE = 0,
|
||||
SEQUENCER_EDIT_STARTEND = 1 << 1,
|
||||
SEQUENCER_CHANGE_FRAME = 1 << 3,
|
||||
SEQUENCER_ADD = 1 << 4,
|
||||
SEQUENCER_DEL = 1 << 5,
|
||||
SEQUENCER_COPYPASTE = 1 << 6,
|
||||
SEQUENCER_EDIT_ALL = SEQUENCER_EDIT_STARTEND | SEQUENCER_CHANGE_FRAME
|
||||
};
|
||||
|
||||
struct SequenceInterface
|
||||
{
|
||||
bool focused = false;
|
||||
virtual int GetFrameMin() const = 0;
|
||||
virtual int GetFrameMax() const = 0;
|
||||
virtual int GetItemCount() const = 0;
|
||||
|
||||
virtual void BeginEdit(int /*index*/) {}
|
||||
virtual void EndEdit() {}
|
||||
virtual int GetItemTypeCount() const { return 0; }
|
||||
virtual const char* GetItemTypeName(int /*typeIndex*/) const { return ""; }
|
||||
virtual const char* GetItemLabel(int /*index*/) const { return ""; }
|
||||
virtual const char* GetCollapseFmt() const { return "%d Frames / %d entries"; }
|
||||
|
||||
virtual void Get(int index, int** start, int** end, int* type, unsigned int* color) = 0;
|
||||
virtual void Add(int /*type*/) {}
|
||||
virtual void Del(int /*index*/) {}
|
||||
virtual void Duplicate(int /*index*/) {}
|
||||
|
||||
virtual void Copy() {}
|
||||
virtual void Paste() {}
|
||||
|
||||
virtual size_t GetCustomHeight(int /*index*/) { return 0; }
|
||||
virtual void DoubleClick(int /*index*/) {}
|
||||
virtual void CustomDraw(int /*index*/, ImDrawList* /*draw_list*/, const ImRect& /*rc*/, const ImRect& /*legendRect*/, const ImRect& /*clippingRect*/, const ImRect& /*legendClippingRect*/) {}
|
||||
virtual void CustomDrawCompact(int /*index*/, ImDrawList* /*draw_list*/, const ImRect& /*rc*/, const ImRect& /*clippingRect*/) {}
|
||||
|
||||
virtual ~SequenceInterface() = default;
|
||||
};
|
||||
|
||||
|
||||
// return true if selection is made
|
||||
bool Sequencer(SequenceInterface* sequence, int* currentFrame, bool* expanded, int* selectedEntry, int* firstFrame, int sequenceOptions);
|
||||
|
||||
}
|
524
main.cpp
524
main.cpp
@ -8,12 +8,20 @@
|
||||
#include "imgui_impl_glfw.h"
|
||||
#include "imgui_impl_opengl3.h"
|
||||
#include <atomic>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <fstream>
|
||||
#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();
|
||||
void renderTestImGui();
|
||||
|
||||
|
||||
// ui timeline stuff
|
||||
@ -27,6 +35,18 @@ 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<float> audioSamples;
|
||||
bool audioDataLoaded = false;
|
||||
|
||||
std::vector<float> downsampledAudioSamples;
|
||||
|
||||
const int num_channels = 512;
|
||||
unsigned char buffer[num_channels] = { 0 };
|
||||
@ -83,6 +103,11 @@ int main() {
|
||||
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);
|
||||
@ -99,9 +124,13 @@ int main() {
|
||||
ImGui_ImplGlfw_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
|
||||
renderImGui();
|
||||
// renderImGui();
|
||||
// renderImGuiBpm();
|
||||
|
||||
renderTestImGui();
|
||||
|
||||
ImGui::Render();
|
||||
|
||||
int display_w, display_h;
|
||||
glfwGetFramebufferSize(window, &display_w, &display_h);
|
||||
glViewport(0, 0, display_w, display_h);
|
||||
@ -122,6 +151,8 @@ int main() {
|
||||
glfwTerminate();
|
||||
CloseHandle(hSerial);
|
||||
|
||||
ma_engine_uninit(&engine);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -144,6 +175,144 @@ void sendDMXData(HANDLE hSerial, unsigned char* data, int length) {
|
||||
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<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;
|
||||
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<float>(),
|
||||
element["r"].get<unsigned char>(),
|
||||
element["g"].get<unsigned char>(),
|
||||
element["b"].get<unsigned char>()
|
||||
);
|
||||
}
|
||||
}
|
||||
else {
|
||||
std::cerr << "Could not open file for reading: " << filename << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
if (filename) {
|
||||
return std::string(filename);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
GLFWwindow* initOpenGL() {
|
||||
if (!glfwInit()) {
|
||||
std::cerr << "Failed to initialize GLFW" << std::endl;
|
||||
@ -170,6 +339,7 @@ GLFWwindow* initOpenGL() {
|
||||
return window;
|
||||
}
|
||||
|
||||
|
||||
void setupImGui(GLFWwindow* window) {
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
@ -182,9 +352,6 @@ void setupImGui(GLFWwindow* window) {
|
||||
}
|
||||
|
||||
void renderImGui() {
|
||||
// Calculate time per beat based on BPM
|
||||
float beat_interval = 60.0f / bpm;
|
||||
|
||||
// Temporary variables to hold the slider values
|
||||
int channel1 = buffer[0];
|
||||
int channel2 = buffer[1];
|
||||
@ -193,6 +360,45 @@ void renderImGui() {
|
||||
// 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);
|
||||
@ -206,13 +412,45 @@ void renderImGui() {
|
||||
// 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<float>(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<ma_uint64>((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)
|
||||
@ -221,13 +459,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;
|
||||
@ -244,12 +507,30 @@ void renderImGui() {
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
@ -268,23 +549,25 @@ void renderImGui() {
|
||||
current_time = static_cast<float>(glfwGetTime()) - playback_start_time;
|
||||
timeline_position = current_time;
|
||||
|
||||
// Trigger markers during playback
|
||||
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) { // Slight tolerance for timing precision
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// Stop playback when the timeline reaches the end
|
||||
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;
|
||||
@ -299,5 +582,230 @@ void renderImGui() {
|
||||
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<float>(glfwGetTime()) - timeline_position - offset_seconds;
|
||||
has_applied_offset = true;
|
||||
}
|
||||
else {
|
||||
playback_start_time = static_cast<float>(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<ma_uint64>((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<int>((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<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;
|
||||
|
||||
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<float>(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();
|
||||
}
|
92621
miniaudio.h
Normal file
92621
miniaudio.h
Normal file
File diff suppressed because it is too large
Load Diff
33
notes.md
Normal file
33
notes.md
Normal file
@ -0,0 +1,33 @@
|
||||
https://github.com/epezent/implot
|
||||
licences for tinyfiledialog and miniaudio
|
||||
|
||||
Might need drigers?
|
||||
https://ftdichip.com/drivers/
|
||||
|
||||
On windows you can find the com port stuff using the `mode` command:
|
||||
```
|
||||
C:\Users\illyum>mode
|
||||
|
||||
Status for device COM3:
|
||||
-----------------------
|
||||
Baud: 1200
|
||||
Parity: None
|
||||
Data Bits: 7
|
||||
Stop Bits: 1
|
||||
Timeout: OFF
|
||||
XON/XOFF: OFF
|
||||
CTS handshaking: OFF
|
||||
DSR handshaking: OFF
|
||||
DSR sensitivity: OFF
|
||||
DTR circuit: ON
|
||||
RTS circuit: ON
|
||||
|
||||
|
||||
Status for device CON:
|
||||
----------------------
|
||||
Lines: 9001
|
||||
Columns: 135
|
||||
Keyboard rate: 31
|
||||
Keyboard delay: 1
|
||||
Code page: 437
|
||||
```
|
8171
tinyfiledialogs.c
Normal file
8171
tinyfiledialogs.c
Normal file
File diff suppressed because it is too large
Load Diff
314
tinyfiledialogs.h
Normal file
314
tinyfiledialogs.h
Normal file
@ -0,0 +1,314 @@
|
||||
/* SPDX-License-Identifier: Zlib
|
||||
Copyright (c) 2014 - 2024 Guillaume Vareille http://ysengrin.com
|
||||
____________________________________________________________________
|
||||
| |
|
||||
| 100% compatible C C++ -> You can rename tinfiledialogs.c as .cpp |
|
||||
|____________________________________________________________________|
|
||||
|
||||
********* TINY FILE DIALOGS OFFICIAL WEBSITE IS ON SOURCEFORGE *********
|
||||
_________
|
||||
/ \ tinyfiledialogs.h v3.18.2 [Jun 8, 2024]
|
||||
|tiny file| Unique header file created [November 9, 2014]
|
||||
| dialogs |
|
||||
\____ ___/ http://tinyfiledialogs.sourceforge.net
|
||||
\| git clone http://git.code.sf.net/p/tinyfiledialogs/code tinyfd
|
||||
____________________________________________
|
||||
| |
|
||||
| email: tinyfiledialogs at ysengrin.com |
|
||||
|____________________________________________|
|
||||
________________________________________________________________________________
|
||||
| ____________________________________________________________________________ |
|
||||
| | | |
|
||||
| | - in tinyfiledialogs, char is UTF-8 by default (since v3.6) | |
|
||||
| | | |
|
||||
| | on windows: | |
|
||||
| | - for UTF-16, use the wchar_t functions at the bottom of the header file | |
|
||||
| | | |
|
||||
| | - _wfopen() requires wchar_t | |
|
||||
| | - fopen() uses char but expects ASCII or MBCS (not UTF-8) | |
|
||||
| | - if you want char to be MBCS: set tinyfd_winUtf8 to 0 | |
|
||||
| | | |
|
||||
| | - alternatively, tinyfiledialogs provides | |
|
||||
| | functions to convert between UTF-8, UTF-16 and MBCS | |
|
||||
| |____________________________________________________________________________| |
|
||||
|________________________________________________________________________________|
|
||||
|
||||
If you like tinyfiledialogs, please upvote my stackoverflow answer
|
||||
https://stackoverflow.com/a/47651444
|
||||
|
||||
- License -
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
__________________________________________
|
||||
| ______________________________________ |
|
||||
| | | |
|
||||
| | DO NOT USE USER INPUT IN THE DIALOGS | |
|
||||
| |______________________________________| |
|
||||
|__________________________________________|
|
||||
*/
|
||||
|
||||
#ifndef TINYFILEDIALOGS_H
|
||||
#define TINYFILEDIALOGS_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/******************************************************************************************************/
|
||||
/**************************************** UTF-8 on Windows ********************************************/
|
||||
/******************************************************************************************************/
|
||||
#ifdef _WIN32
|
||||
/* On windows, if you want to use UTF-8 ( instead of the UTF-16/wchar_t functions at the end of this file )
|
||||
Make sure your code is really prepared for UTF-8 (on windows, functions like fopen() expect MBCS and not UTF-8) */
|
||||
extern int tinyfd_winUtf8; /* on windows char strings can be 1:UTF-8(default) or 0:MBCS */
|
||||
/* for MBCS change this to 0, in tinyfiledialogs.c or in your code */
|
||||
|
||||
/* Here are some functions to help you convert between UTF-16 UTF-8 MBSC */
|
||||
char * tinyfd_utf8toMbcs(char const * aUtf8string);
|
||||
char * tinyfd_utf16toMbcs(wchar_t const * aUtf16string);
|
||||
wchar_t * tinyfd_mbcsTo16(char const * aMbcsString);
|
||||
char * tinyfd_mbcsTo8(char const * aMbcsString);
|
||||
wchar_t * tinyfd_utf8to16(char const * aUtf8string);
|
||||
char * tinyfd_utf16to8(wchar_t const * aUtf16string);
|
||||
#endif
|
||||
/******************************************************************************************************/
|
||||
/******************************************************************************************************/
|
||||
/******************************************************************************************************/
|
||||
|
||||
/************* 3 funtions for C# (you don't need this in C or C++) : */
|
||||
char const * tinyfd_getGlobalChar(char const * aCharVariableName); /* returns NULL on error */
|
||||
int tinyfd_getGlobalInt(char const * aIntVariableName); /* returns -1 on error */
|
||||
int tinyfd_setGlobalInt(char const * aIntVariableName, int aValue); /* returns -1 on error */
|
||||
/* aCharVariableName: "tinyfd_version" "tinyfd_needs" "tinyfd_response"
|
||||
aIntVariableName : "tinyfd_verbose" "tinyfd_silent" "tinyfd_allowCursesDialogs"
|
||||
"tinyfd_forceConsole" "tinyfd_assumeGraphicDisplay" "tinyfd_winUtf8"
|
||||
**************/
|
||||
|
||||
extern char tinyfd_version[8]; /* contains tinyfd current version number */
|
||||
extern char tinyfd_needs[]; /* info about requirements */
|
||||
extern int tinyfd_verbose; /* 0 (default) or 1 : on unix, prints the command line calls */
|
||||
extern int tinyfd_silent; /* 1 (default) or 0 : on unix, hide errors and warnings from called dialogs */
|
||||
|
||||
/** Curses dialogs are difficult to use and counter-intuitive.
|
||||
On windows they are only ascii and still uses the unix backslash ! **/
|
||||
extern int tinyfd_allowCursesDialogs; /* 0 (default) or 1 */
|
||||
|
||||
extern int tinyfd_forceConsole; /* 0 (default) or 1 */
|
||||
/* for unix & windows: 0 (graphic mode) or 1 (console mode).
|
||||
0: try to use a graphic solution, if it fails then it uses console mode.
|
||||
1: forces all dialogs into console mode even when an X server is present.
|
||||
if enabled, it can use the package Dialog or dialog.exe.
|
||||
on windows it only make sense for console applications */
|
||||
|
||||
extern int tinyfd_assumeGraphicDisplay; /* 0 (default) or 1 */
|
||||
/* some systems don't set the environment variable DISPLAY even when a graphic display is present.
|
||||
set this to 1 to tell tinyfiledialogs to assume the existence of a graphic display */
|
||||
|
||||
extern char tinyfd_response[1024];
|
||||
/* if you pass "tinyfd_query" as aTitle,
|
||||
the functions will not display the dialogs
|
||||
but will return 0 for console mode, 1 for graphic mode.
|
||||
tinyfd_response is then filled with the retain solution.
|
||||
possible values for tinyfd_response are (all lowercase)
|
||||
for graphic mode:
|
||||
windows_wchar windows applescript kdialog zenity zenity3 yad matedialog
|
||||
shellementary qarma python2-tkinter python3-tkinter python-dbus
|
||||
perl-dbus gxmessage gmessage xmessage xdialog gdialog dunst
|
||||
for console mode:
|
||||
dialog whiptail basicinput no_solution */
|
||||
|
||||
void tinyfd_beep(void);
|
||||
|
||||
int tinyfd_notifyPopup(
|
||||
char const * aTitle, /* NULL or "" */
|
||||
char const * aMessage, /* NULL or "" may contain \n \t */
|
||||
char const * aIconType); /* "info" "warning" "error" */
|
||||
/* return has only meaning for tinyfd_query */
|
||||
|
||||
int tinyfd_messageBox(
|
||||
char const * aTitle , /* NULL or "" */
|
||||
char const * aMessage , /* NULL or "" may contain \n \t */
|
||||
char const * aDialogType , /* "ok" "okcancel" "yesno" "yesnocancel" */
|
||||
char const * aIconType , /* "info" "warning" "error" "question" */
|
||||
int aDefaultButton ) ;
|
||||
/* 0 for cancel/no , 1 for ok/yes , 2 for no in yesnocancel */
|
||||
|
||||
char * tinyfd_inputBox(
|
||||
char const * aTitle , /* NULL or "" */
|
||||
char const * aMessage , /* NULL or "" (\n and \t have no effect) */
|
||||
char const * aDefaultInput ) ; /* NULL = passwordBox, "" = inputbox */
|
||||
/* returns NULL on cancel */
|
||||
|
||||
char * tinyfd_saveFileDialog(
|
||||
char const * aTitle , /* NULL or "" */
|
||||
char const * aDefaultPathAndOrFile , /* NULL or "" , ends with / to set only a directory */
|
||||
int aNumOfFilterPatterns , /* 0 (1 in the following example) */
|
||||
char const * const * aFilterPatterns , /* NULL or char const * lFilterPatterns[1]={"*.txt"} */
|
||||
char const * aSingleFilterDescription ) ; /* NULL or "text files" */
|
||||
/* returns NULL on cancel */
|
||||
|
||||
char * tinyfd_openFileDialog(
|
||||
char const * aTitle, /* NULL or "" */
|
||||
char const * aDefaultPathAndOrFile, /* NULL or "" , ends with / to set only a directory */
|
||||
int aNumOfFilterPatterns , /* 0 (2 in the following example) */
|
||||
char const * const * aFilterPatterns, /* NULL or char const * lFilterPatterns[2]={"*.png","*.jpg"}; */
|
||||
char const * aSingleFilterDescription, /* NULL or "image files" */
|
||||
int aAllowMultipleSelects ) ; /* 0 or 1 */
|
||||
/* in case of multiple files, the separator is | */
|
||||
/* returns NULL on cancel */
|
||||
|
||||
char * tinyfd_selectFolderDialog(
|
||||
char const * aTitle, /* NULL or "" */
|
||||
char const * aDefaultPath); /* NULL or "" */
|
||||
/* returns NULL on cancel */
|
||||
|
||||
char * tinyfd_colorChooser(
|
||||
char const * aTitle, /* NULL or "" */
|
||||
char const * aDefaultHexRGB, /* NULL or "" or "#FF0000" */
|
||||
unsigned char const aDefaultRGB[3] , /* unsigned char lDefaultRGB[3] = { 0 , 128 , 255 }; */
|
||||
unsigned char aoResultRGB[3] ) ; /* unsigned char lResultRGB[3]; */
|
||||
/* aDefaultRGB is used only if aDefaultHexRGB is absent */
|
||||
/* aDefaultRGB and aoResultRGB can be the same array */
|
||||
/* returns NULL on cancel */
|
||||
/* returns the hexcolor as a string "#FF0000" */
|
||||
/* aoResultRGB also contains the result */
|
||||
|
||||
|
||||
/************ WINDOWS ONLY SECTION ************************/
|
||||
#ifdef _WIN32
|
||||
|
||||
/* windows only - utf-16 version */
|
||||
int tinyfd_notifyPopupW(
|
||||
wchar_t const * aTitle, /* NULL or L"" */
|
||||
wchar_t const * aMessage, /* NULL or L"" may contain \n \t */
|
||||
wchar_t const * aIconType); /* L"info" L"warning" L"error" */
|
||||
|
||||
/* windows only - utf-16 version */
|
||||
int tinyfd_messageBoxW(
|
||||
wchar_t const * aTitle, /* NULL or L"" */
|
||||
wchar_t const * aMessage, /* NULL or L"" may contain \n \t */
|
||||
wchar_t const * aDialogType, /* L"ok" L"okcancel" L"yesno" */
|
||||
wchar_t const * aIconType, /* L"info" L"warning" L"error" L"question" */
|
||||
int aDefaultButton ); /* 0 for cancel/no , 1 for ok/yes */
|
||||
/* returns 0 for cancel/no , 1 for ok/yes */
|
||||
|
||||
/* windows only - utf-16 version */
|
||||
wchar_t * tinyfd_inputBoxW(
|
||||
wchar_t const * aTitle, /* NULL or L"" */
|
||||
wchar_t const * aMessage, /* NULL or L"" (\n nor \t not respected) */
|
||||
wchar_t const * aDefaultInput); /* NULL passwordBox, L"" inputbox */
|
||||
|
||||
/* windows only - utf-16 version */
|
||||
wchar_t * tinyfd_saveFileDialogW(
|
||||
wchar_t const * aTitle, /* NULL or L"" */
|
||||
wchar_t const * aDefaultPathAndOrFile, /* NULL or L"" , ends with / to set only a directory */
|
||||
int aNumOfFilterPatterns, /* 0 (1 in the following example) */
|
||||
wchar_t const * const * aFilterPatterns, /* NULL or wchar_t const * lFilterPatterns[1]={L"*.txt"} */
|
||||
wchar_t const * aSingleFilterDescription); /* NULL or L"text files" */
|
||||
/* returns NULL on cancel */
|
||||
|
||||
/* windows only - utf-16 version */
|
||||
wchar_t * tinyfd_openFileDialogW(
|
||||
wchar_t const * aTitle, /* NULL or L"" */
|
||||
wchar_t const * aDefaultPathAndOrFile, /* NULL or L"" , ends with / to set only a directory */
|
||||
int aNumOfFilterPatterns , /* 0 (2 in the following example) */
|
||||
wchar_t const * const * aFilterPatterns, /* NULL or wchar_t const * lFilterPatterns[2]={L"*.png","*.jpg"} */
|
||||
wchar_t const * aSingleFilterDescription, /* NULL or L"image files" */
|
||||
int aAllowMultipleSelects ) ; /* 0 or 1 */
|
||||
/* in case of multiple files, the separator is | */
|
||||
/* returns NULL on cancel */
|
||||
|
||||
/* windows only - utf-16 version */
|
||||
wchar_t * tinyfd_selectFolderDialogW(
|
||||
wchar_t const * aTitle, /* NULL or L"" */
|
||||
wchar_t const * aDefaultPath); /* NULL or L"" */
|
||||
/* returns NULL on cancel */
|
||||
|
||||
/* windows only - utf-16 version */
|
||||
wchar_t * tinyfd_colorChooserW(
|
||||
wchar_t const * aTitle, /* NULL or L"" */
|
||||
wchar_t const * aDefaultHexRGB, /* NULL or L"#FF0000" */
|
||||
unsigned char const aDefaultRGB[3], /* unsigned char lDefaultRGB[3] = { 0 , 128 , 255 }; */
|
||||
unsigned char aoResultRGB[3]); /* unsigned char lResultRGB[3]; */
|
||||
/* returns the hexcolor as a string L"#FF0000" */
|
||||
/* aoResultRGB also contains the result */
|
||||
/* aDefaultRGB is used only if aDefaultHexRGB is NULL */
|
||||
/* aDefaultRGB and aoResultRGB can be the same array */
|
||||
/* returns NULL on cancel */
|
||||
|
||||
#endif /*_WIN32 */
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /*extern "C"*/
|
||||
#endif
|
||||
|
||||
#endif /* TINYFILEDIALOGS_H */
|
||||
|
||||
/*
|
||||
________________________________________________________________________________
|
||||
| ____________________________________________________________________________ |
|
||||
| | | |
|
||||
| | on windows: | |
|
||||
| | - for UTF-16, use the wchar_t functions at the bottom of the header file | |
|
||||
| | - _wfopen() requires wchar_t | |
|
||||
| | | |
|
||||
| | - in tinyfiledialogs, char is UTF-8 by default (since v3.6) | |
|
||||
| | - but fopen() expects MBCS (not UTF-8) | |
|
||||
| | - if you want char to be MBCS: set tinyfd_winUtf8 to 0 | |
|
||||
| | | |
|
||||
| | - alternatively, tinyfiledialogs provides | |
|
||||
| | functions to convert between UTF-8, UTF-16 and MBCS | |
|
||||
| |____________________________________________________________________________| |
|
||||
|________________________________________________________________________________|
|
||||
|
||||
- This is not for ios nor android (it works in termux though).
|
||||
- The files can be renamed with extension ".cpp" as the code is 100% compatible C C++
|
||||
(just comment out << extern "C" >> in the header file)
|
||||
- Windows is fully supported from XP to 10 (maybe even older versions)
|
||||
- C# & LUA via dll, see files in the folder EXTRAS
|
||||
- OSX supported from 10.4 to latest (maybe even older versions)
|
||||
- Do not use " and ' as the dialogs will be displayed with a warning
|
||||
instead of the title, message, etc...
|
||||
- There's one file filter only, it may contain several patterns.
|
||||
- If no filter description is provided,
|
||||
the list of patterns will become the description.
|
||||
- On windows link against Comdlg32.lib and Ole32.lib
|
||||
(on windows the no linking claim is a lie)
|
||||
- On unix: it tries command line calls, so no such need (NO LINKING).
|
||||
- On unix you need one of the following:
|
||||
applescript, kdialog, zenity, matedialog, shellementary, qarma, yad,
|
||||
python (2 or 3)/tkinter/python-dbus (optional), Xdialog
|
||||
or curses dialogs (opens terminal if running without console).
|
||||
- One of those is already included on most (if not all) desktops.
|
||||
- In the absence of those it will use gdialog, gxmessage or whiptail
|
||||
with a textinputbox. If nothing is found, it switches to basic console input,
|
||||
it opens a console if needed (requires xterm + bash).
|
||||
- for curses dialogs you must set tinyfd_allowCursesDialogs=1
|
||||
- You can query the type of dialog that will be used (pass "tinyfd_query" as aTitle)
|
||||
- String memory is preallocated statically for all the returned values.
|
||||
- File and path names are tested before return, they should be valid.
|
||||
- tinyfd_forceConsole=1; at run time, forces dialogs into console mode.
|
||||
- On windows, console mode only make sense for console applications.
|
||||
- On windows, console mode is not implemented for wchar_T UTF-16.
|
||||
- Mutiple selects are not possible in console mode.
|
||||
- The package dialog must be installed to run in curses dialogs in console mode.
|
||||
It is already installed on most unix systems.
|
||||
- On osx, the package dialog can be installed via
|
||||
http://macappstore.org/dialog or http://macports.org
|
||||
- On windows, for curses dialogs console mode,
|
||||
dialog.exe should be copied somewhere on your executable path.
|
||||
It can be found at the bottom of the following page:
|
||||
http://andrear.altervista.org/home/cdialog.php
|
||||
*/
|
Loading…
x
Reference in New Issue
Block a user