Skip to content

Commit

Permalink
WIP - new geWaveform
Browse files Browse the repository at this point in the history
  • Loading branch information
gvnnz committed Dec 15, 2024
1 parent 7d8ad49 commit a5422ce
Show file tree
Hide file tree
Showing 4 changed files with 307 additions and 2 deletions.
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
212 changes: 212 additions & 0 deletions src/gui/elems/sampleEditor/waveform.cpp
Original file line number Diff line number Diff line change
@@ -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
* <http://www.gnu.org/licenses/>.
*
* -------------------------------------------------------------------------- */

#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<Frame> 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<double>(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<double>(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<double> 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<int> 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
91 changes: 91 additions & 0 deletions src/gui/elems/sampleEditor/waveform.h
Original file line number Diff line number Diff line change
@@ -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
* <http://www.gnu.org/licenses/>.
*
* -------------------------------------------------------------------------- */

#ifndef GE_WAVEFORM_H
#define GE_WAVEFORM_H

#include "core/types.h"
#include "deps/geompp/src/range.hpp"
#include <FL/Fl_Widget.H>
#include <utility>
#include <vector>

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<float, float>;
using Peaks = std::vector<std::vector<Peak>>;

Data() = default;
Data(const m::Wave&, geompp::Range<Frame>, 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>, 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<Frame> m_range;
Data m_data;
};
} // namespace giada::v

#endif
4 changes: 2 additions & 2 deletions src/gui/elems/sampleEditor/waveform_DEPR_.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down

0 comments on commit a5422ce

Please sign in to comment.