From a5422ceb24e1c2b7e4fab2b6469d09443cc72b96 Mon Sep 17 00:00:00 2001 From: gvnnz Date: Thu, 12 Dec 2024 15:48:16 +0100 Subject: [PATCH] WIP - new geWaveform --- CMakeLists.txt | 2 + src/gui/elems/sampleEditor/waveform.cpp | 212 ++++++++++++++++++++ src/gui/elems/sampleEditor/waveform.h | 91 +++++++++ src/gui/elems/sampleEditor/waveform_DEPR_.h | 4 +- 4 files changed, 307 insertions(+), 2 deletions(-) create mode 100644 src/gui/elems/sampleEditor/waveform.cpp create mode 100644 src/gui/elems/sampleEditor/waveform.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 712dd50c7..8d39c44ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -281,6 +281,8 @@ list(APPEND SOURCES src/gui/elems/plugin/pluginElement.h src/gui/elems/sampleEditor/waveform_DEPR_.cpp src/gui/elems/sampleEditor/waveform_DEPR_.h + src/gui/elems/sampleEditor/waveform.cpp + src/gui/elems/sampleEditor/waveform.h src/gui/elems/sampleEditor/waveTools.cpp src/gui/elems/sampleEditor/waveTools.h src/gui/elems/volumeTool.cpp diff --git a/src/gui/elems/sampleEditor/waveform.cpp b/src/gui/elems/sampleEditor/waveform.cpp new file mode 100644 index 000000000..d057ca767 --- /dev/null +++ b/src/gui/elems/sampleEditor/waveform.cpp @@ -0,0 +1,212 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2024 Giovanni A. Zuliani | Monocasual Laboratories + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + +#include "gui/elems/sampleEditor/waveform.h" +#include "core/const.h" +#include "core/wave.h" +#include "deps/mcl-audio-buffer/src/audioBuffer.hpp" +#include "gui/drawing.h" +#include "utils/log.h" +#include "utils/math.h" + +namespace giada::v +{ +geWaveform::Data::Data(const m::Wave& wave, geompp::Range rangeAudio, int widthScreen) +{ + const mcl::AudioBuffer& audioBuffer = wave.getBuffer(); + + /* ratio + Defines the ratio between the audio content and the screen width. If < 1.0, + we are digging between individual samples. If >= 1.0, it also tells how many + audio frames we should consider as a 'chunk' to use for a single column in + screen space. */ + + const double ratio = rangeAudio.getLength() / static_cast(widthScreen); + + m_peaks.resize(audioBuffer.countChannels(), {}); + + for (int channel = 0; channel < audioBuffer.countChannels(); channel++) + { + m_peaks[channel].resize(widthScreen, {0.0f, 0.0f}); + + if (ratio >= 1) + { + double startFrame = rangeAudio.a; + for (int column = 0; column < widthScreen; column++) + { + m_peaks[channel][column] = getPeak(wave, {startFrame, startFrame + ratio}, ratio, channel); + startFrame += ratio; + } + } + else + { + const double columnsPerFrame = widthScreen / static_cast(rangeAudio.getLength()); + double column = 0; + for (int frame = rangeAudio.a; frame < rangeAudio.b; frame++) + { + m_peaks[channel][column] = getPeak(wave, frame, channel); + column += columnsPerFrame; + } + } + } +} + +/* -------------------------------------------------------------------------- */ + +geWaveform::Data::Peak geWaveform::Data::getPeak(const m::Wave& wave, geompp::Range range, double steps, int channel) const +{ + const mcl::AudioBuffer& audioBuffer = wave.getBuffer(); + float peakUp = 0.0; + float peakDown = 0.0; + + for (double frame = range.a; frame < std::floor(range.a + steps); frame++) + { + const float value = audioBuffer[frame][channel]; + peakUp = std::max(value, peakUp); + peakDown = std::min(value, peakDown); + } + + return {peakUp, peakDown}; +} + +/* -------------------------------------------------------------------------- */ + +geWaveform::Data::Peak geWaveform::Data::getPeak(const m::Wave& wave, Frame frame, int channel) const +{ + const mcl::AudioBuffer& audioBuffer = wave.getBuffer(); + const float value = audioBuffer[frame][channel]; + const float peakUp = value >= 0.0f ? value : 0.0f; + const float peakDown = value < 0.0f ? value : 0.0f; + + return {peakUp, peakDown}; +} + +/* -------------------------------------------------------------------------- */ + +const geWaveform::Data::Peaks& geWaveform::Data::get() const +{ + return m_peaks; +} + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + +geWaveform::geWaveform() +: Fl_Widget(0, 0, 0, 0) +, m_wave(nullptr) +{ +} + +/* -------------------------------------------------------------------------- */ + +void geWaveform::draw() +{ + const geompp::Rect bounds = {x(), y(), w(), h()}; + + v::drawRectf(bounds, G_COLOR_GREY_2); // Blank canvas + v::drawRect(bounds, G_COLOR_GREY_4); // Border + + if (m_data.get().size() == 0) + { + drawText("No audio data.", bounds, FL_HELVETICA, G_GUI_FONT_SIZE_BASE, G_COLOR_GREY_4); + return; + } + + fl_color(G_COLOR_BLACK); + + const int channelHeight = h() / m_data.get().size(); + + for (int numChannel = 0; const auto& channel : m_data.get()) + { + const int channelY = (channelHeight * numChannel) + y(); + const int zero = channelY + (channelHeight / 2); + + drawLine(bounds.reduced({1}).getWidthAsLine().withY(zero), G_COLOR_BLACK); // Draw zero line + + for (int column = x() + 1; const auto& peaks : channel) // x() + 1: take care of 1px border + { + if (column > bounds.xw - 2) // Don't overflow paint + break; + + const float peakUpAudio = peaks.first; + const float peakDownAudio = peaks.second; + const int peakUpScreen = u::math::map(peakUpAudio, 0.0f, 1.0f, zero, channelY); + const int peakDownScreen = u::math::map(peakDownAudio, 0.0f, -1.0f, zero, channelY + channelHeight); + + fl_line(column, zero, column, peakUpScreen); + fl_line(column, zero, column, peakDownScreen); + + column++; + } + + numChannel++; + } +} + +/* -------------------------------------------------------------------------- */ + +void geWaveform::setWave(const m::Wave* w) +{ + m_wave = w; + m_range = {0, m_wave == nullptr ? 0 : m_wave->getBuffer().countFrames()}; + + if (m_wave != nullptr) + invalidate(); +} + +/* -------------------------------------------------------------------------- */ + +void geWaveform::zoomIn() +{ + m_range.b = std::max(m_range.b / 2, 1); + + invalidate(); + redraw(); +} + +/* -------------------------------------------------------------------------- */ + +void geWaveform::zoomOut() +{ + m_range.b = std::min(m_range.b * 2, m_wave->getBuffer().countFrames()); + + invalidate(); + redraw(); +} + +/* -------------------------------------------------------------------------- */ + +void geWaveform::invalidate() +{ + assert(m_wave != nullptr); + + G_DEBUG("Invalidate waveform data"); + + m_data = {*m_wave, m_range, w() - 2}; // -2: take care of 1px border +} + +} // namespace giada::v \ No newline at end of file diff --git a/src/gui/elems/sampleEditor/waveform.h b/src/gui/elems/sampleEditor/waveform.h new file mode 100644 index 000000000..5e7d7b973 --- /dev/null +++ b/src/gui/elems/sampleEditor/waveform.h @@ -0,0 +1,91 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2024 Giovanni A. Zuliani | Monocasual Laboratories + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + +#ifndef GE_WAVEFORM_H +#define GE_WAVEFORM_H + +#include "core/types.h" +#include "deps/geompp/src/range.hpp" +#include +#include +#include + +namespace giada::m +{ +class Wave; +} + +namespace giada::v +{ +class geWaveform : public Fl_Widget +{ +public: + geWaveform(); + + void draw() override; + + void setWave(const m::Wave*); + void zoomIn(); + void zoomOut(); + +private: + class Data + { + public: + using Peak = std::pair; + using Peaks = std::vector>; + + Data() = default; + Data(const m::Wave&, geompp::Range, int width); + + const Peaks& get() const; + + private: + /* getPeak (1) + Returns the Peak pair given a range of audio data to parse from Wave. + It computes the highest/lowest value in that audio range. Used when + ratio >= 1. */ + + Peak getPeak(const m::Wave&, geompp::Range, double steps, int channel) const; + + /* getPeak (2) + Returs the Peak pair given a single frame of audio data to read from + Wave. Used when ratio < 1. */ + + Peak getPeak(const m::Wave&, Frame, int channel) const; + + Peaks m_peaks; + }; + + void invalidate(); + + const m::Wave* m_wave; + geompp::Range m_range; + Data m_data; +}; +} // namespace giada::v + +#endif diff --git a/src/gui/elems/sampleEditor/waveform_DEPR_.h b/src/gui/elems/sampleEditor/waveform_DEPR_.h index 830eb3715..f89cbb1b1 100644 --- a/src/gui/elems/sampleEditor/waveform_DEPR_.h +++ b/src/gui/elems/sampleEditor/waveform_DEPR_.h @@ -24,8 +24,8 @@ * * -------------------------------------------------------------------------- */ -#ifndef GE_WAVEFORM_H -#define GE_WAVEFORM_H +#ifndef GE_WAVEFORM_DEPR_H +#define GE_WAVEFORM_DEPR_H #include "core/const.h" #include "core/types.h"