1
0
mirror of synced 2025-01-09 13:11:38 +01:00
ImHex/plugins/visualizers/source/content/pl_visualizers/sound.cpp
paxcut 72822d03aa
fix: Stereo sound visualizations (#1970)
Even tough the sound visualizer has `channels` as one of its parameters
it wasn't using it properly.

### Problem description
The biggest problem is that at each frame the index was being advanced
per channel frame_count increments. The number of channels also
determines how many graph will be needed to display the graphs of the
visualized sound files. Besides these two problems there were many
others like incorrect playback time, cracking audio, etc. which will not
be mentioned.

### Implementation description
To sample the signal a channel sampler was created based on the one used
previously that returns as many sampled signals as there are channels.
This PR aims hopefully at fixing all the problems encountered, and it
has been tested extensively using `Audacity` exported samples to ensure
the visualizer fidelity on playback and graph appearance.

### Screenshots

![image](https://github.com/user-attachments/assets/03453860-693f-4af4-b6c6-e828a102c389)
2024-11-29 17:22:22 +01:00

142 lines
6.0 KiB
C++

#include <hex/helpers/utils.hpp>
#include <content/visualizer_helpers.hpp>
#include <implot.h>
#include <imgui.h>
#include <miniaudio.h>
#include <fonts/codicons_font.h>
#include <hex/api/task_manager.hpp>
#include <hex/ui/imgui_imhex_extensions.h>
namespace hex::plugin::visualizers {
void drawSoundVisualizer(pl::ptrn::Pattern &, bool shouldReset, std::span<const pl::core::Token::Literal> arguments) {
auto wavePattern = arguments[0].toPattern();
auto channels = arguments[1].toUnsigned();
auto sampleRate = arguments[2].toUnsigned();
u32 downSampling = wavePattern->getSize() / 300_scaled / 8 / channels;
static std::vector<i16> waveData;
static std::vector<std::vector<i16>> sampledData;
sampledData.resize(channels);
static ma_device audioDevice;
static ma_device_config deviceConfig;
static bool shouldStop = false;
static u64 index = 0;
static TaskHolder resetTask;
if (sampleRate == 0)
throw std::logic_error(hex::format("Invalid sample rate: {}", sampleRate));
else if (channels == 0)
throw std::logic_error(hex::format("Invalid channel count: {}", channels));
u64 sampledIndex;
if (shouldReset) {
waveData.clear();
resetTask = TaskManager::createTask("hex.visualizers.pl_visualizer.task.visualizing"_lang, TaskManager::NoProgress, [=](Task &) {
ma_device_stop(&audioDevice);
waveData = patternToArray<i16>(wavePattern.get());
if (waveData.empty())
return;
sampledData = sampleChannels(waveData, 300_scaled * 4, channels);
index = 0;
deviceConfig = ma_device_config_init(ma_device_type_playback);
deviceConfig.playback.format = ma_format_s16;
deviceConfig.playback.channels = channels;
deviceConfig.sampleRate = sampleRate;
deviceConfig.pUserData = &waveData;
deviceConfig.dataCallback = [](ma_device *device, void *pOutput, const void *, ma_uint32 frameCount) {
if (index >= waveData.size()) {
index = 0;
shouldStop = true;
return;
}
ma_copy_pcm_frames(pOutput, waveData.data() + index, frameCount, device->playback.format, device->playback.channels);
index += frameCount * device->playback.channels;
};
ma_device_init(nullptr, &deviceConfig, &audioDevice);
});
}
sampledIndex = index / downSampling;
ImGui::BeginDisabled(resetTask.isRunning());
u32 waveDataSize = waveData.size();
u32 sampledDataSize = sampledData[0].size();
auto subplotFlags = ImPlotSubplotFlags_LinkAllX | ImPlotSubplotFlags_LinkCols | ImPlotSubplotFlags_NoResize;
auto plotFlags = ImPlotFlags_CanvasOnly | ImPlotFlags_NoFrame | ImPlotFlags_NoInputs;
auto axisFlags = ImPlotAxisFlags_NoDecorations | ImPlotAxisFlags_NoMenus | ImPlotAxisFlags_AutoFit;
ImPlot::PushStyleVar(ImPlotStyleVar_PlotPadding, ImVec2(0, 0));
if (ImPlot::BeginSubplots("##AxisLinking", channels, 1, scaled(ImVec2(300, 80 * channels)), subplotFlags)) {
for (u32 i = 0; i < channels; i++) {
if (ImPlot::BeginPlot("##amplitude_plot", scaled(ImVec2(300, 80)), plotFlags)) {
ImPlot::SetupAxes("##time", "##amplitude", axisFlags, axisFlags);
double dragPos = sampledIndex;
if (ImPlot::DragLineX(1, &dragPos, ImGui::GetStyleColorVec4(ImGuiCol_Text))) {
if (dragPos < 0) dragPos = 0;
if (dragPos >= sampledDataSize) dragPos = sampledDataSize - 1;
sampledIndex = dragPos;
}
ImPlot::PlotLine("##audio", sampledData[i].data(), sampledDataSize);
ImPlot::EndPlot();
}
}
ImPlot::PopStyleVar();
index = sampledIndex * downSampling;
{
const u64 min = 0, max = sampledDataSize-1;
ImGui::PushItemWidth(300_scaled);
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
ImGui::SliderScalar("##index", ImGuiDataType_U64, &sampledIndex, &min, &max, "");
ImGui::PopStyleVar();
ImGui::PopItemWidth();
}
index = sampledIndex * downSampling;
if (shouldStop) {
shouldStop = false;
ma_device_stop(&audioDevice);
}
bool playing = ma_device_is_started(&audioDevice);
if (ImGuiExt::IconButton(playing ? ICON_VS_DEBUG_PAUSE : ICON_VS_PLAY, ImGuiExt::GetCustomColorVec4(ImGuiCustomCol_ToolbarGreen))) {
if (playing)
ma_device_stop(&audioDevice);
else
ma_device_start(&audioDevice);
}
ImGui::SameLine();
if (ImGuiExt::IconButton(ICON_VS_DEBUG_STOP, ImGuiExt::GetCustomColorVec4(ImGuiCustomCol_ToolbarRed))) {
index = 0;
sampledIndex = 0;
ma_device_stop(&audioDevice);
}
ImGui::EndDisabled();
ImGui::SameLine();
index = sampledIndex * downSampling;
if (resetTask.isRunning())
ImGuiExt::TextSpinner("");
else
ImGuiExt::TextFormatted("{:02d}:{:02d}:{:03d} / {:02d}:{:02d}:{:03d}",
(index / sampleRate / channels) / 60, (index / sampleRate / channels) % 60, (index * 1000 / sampleRate / channels ) % 1000,
((waveDataSize-1) / sampleRate / channels) / 60, ((waveDataSize-1) / sampleRate / channels) % 60, ((waveDataSize-1) * 1000 / sampleRate / channels) % 1000);
ImPlot::EndSubplots();
}
}
}