--------------------------------------------------------------------------------
+0.14.2 --- 2017 . 08 . 14
+- [Sample Editor] Audible preview (with optional loop mode)
+- [Sample Editor] Frame-precise editing
+- [Sample Editor] Show sample's information
+- [Sample Editor] Improved fade out algorithm
+- [Sample Editor] Process both left and right channel's data while drawing
+- Better Wave objects handling
+- Improved channels' memory management
+- Improved empty columns cleanup algorithm
+- Update Catch version
+- Update JUCE version (5.1.1)
+- Update Jansson version (2.10)
+- Fix missing tempo update on reset to init state
+- Fix wrong memory allocation for UI-less plugins
+
+
0.14.1 --- 2017 . 07 . 16
- Update JUCE library to 5.0.2
- Show play head in Sample Editor
src/core/storager.cpp \
src/core/clock.h \
src/core/clock.cpp \
+src/core/waveManager.h \
+src/core/waveManager.cpp \
src/glue/main.h \
src/glue/main.cpp \
src/glue/io.h \
if LINUX
giada_SOURCES += src/deps/rtaudio-mod/RtAudio.h src/deps/rtaudio-mod/RtAudio.cpp
-# -Wno-error=misleading-indentation: don't stop on JUCE warnings on GCC6
# -Wno-error=unused-function: don't stop on JUCE's unused functions
-giada_CXXFLAGS += -Wno-error=misleading-indentation -Wno-error=unused-function
+giada_CXXFLAGS += -Wno-error=unused-function
giada_CPPFLAGS += -D__LINUX_ALSA__ -D__LINUX_PULSE__ -D__UNIX_JACK__
giada_LDADD = -lsndfile -lfltk -lXext -lX11 -lXft -lXpm -lm -ljack -lasound \
-lpthread -ldl -lpulse-simple -lpulse -lsamplerate -lrtmidi -ljansson \
tests/main.cpp \
tests/conf.cpp \
tests/wave.cpp \
+tests/waveManager.cpp \
tests/patch.cpp \
tests/midiMapConf.cpp \
tests/pluginHost.cpp \
tests/utils.cpp \
tests/recorder.cpp \
+tests/waveFx.cpp \
src/core/conf.cpp \
src/core/wave.cpp \
+src/core/waveManager.cpp \
+src/core/waveFx.cpp \
src/core/midiMapConf.cpp \
src/core/patch.cpp \
src/core/plugin.cpp \
: bufferSize (bufferSize),
midiFilter (-1),
pan (0.5f),
+ previewMode (G_PREVIEW_NONE),
type (type),
status (status),
key (0),
midiOutLmute (0x0),
midiOutLsolo (0x0)
{
- vChan = (float *) malloc(bufferSize * sizeof(float));
- if (!vChan)
- gu_log("[Channel::allocVchan] unable to alloc memory for vChan\n");
- std::memset(vChan, 0, bufferSize * sizeof(float));
}
Channel::~Channel()
{
status = STATUS_OFF;
- if (vChan)
- free(vChan);
+ if (vChan != nullptr)
+ delete[] vChan;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool Channel::allocBuffers()
+{
+ vChan = new (std::nothrow) float[bufferSize];
+ if (vChan == nullptr) {
+ gu_log("[Channel::allocBuffers] unable to alloc memory for vChan!\n");
+ return false;
+ }
+ std::memset(vChan, 0, bufferSize * sizeof(float));
+ return true;
}
/* -------------------------------------------------------------------------- */
+void Channel::setPreviewMode(int m)
+{
+ previewMode = m;
+}
+
+
+bool Channel::isPreview()
+{
+ return previewMode != G_PREVIEW_NONE;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
#ifdef WITH_VST
juce::MidiBuffer &Channel::getPluginMidiEvents()
float pan;
+ /* previewMode
+ Whether the channel is in audio preview mode or not. */
+
+ int previewMode;
+
/* sendMidiLMessage
Composes a MIDI message by merging bytes from MidiMap conf class, and sends it
to KernelMidi. */
- void sendMidiLmessage(uint32_t learn, const giada::m::midimap::message_t &msg);
+ void sendMidiLmessage(uint32_t learn, const giada::m::midimap::message_t& msg);
/* calcPanning
Given an audio channel (stereo: 0 or 1) computes the current panning value. */
/* copy
* Make a shallow copy (no vChan/pChan allocation) of another channel. */
- virtual void copy(const Channel *src, pthread_mutex_t *pluginMutex) = 0;
+ virtual void copy(const Channel* src, pthread_mutex_t* pluginMutex) = 0;
/* readPatch
* Fill channel with data from patch. */
- virtual int readPatch(const std::string &basePath, int i,
- pthread_mutex_t *pluginMutex, int samplerate, int rsmpQuality);
+ virtual int readPatch(const std::string& basePath, int i,
+ pthread_mutex_t* pluginMutex, int samplerate, int rsmpQuality);
/* process
Merges vChannels into buffer, plus plugin processing (if any). Warning:
inBuffer might be nullptr if no input devices are available for recording. */
- virtual void process(float *outBuffer, float *inBuffer) = 0;
+ virtual void process(float* outBuffer, float* inBuffer) = 0;
+
+ /* Preview
+ Makes itself audibile for audio preview, such as Sample Editor or other
+ tools. */
+
+ virtual void preview(float* outBuffer) = 0;
/* start
Action to do when channel starts. doQuantize = false (don't quantize)
// TODO - quantize is useless!
- virtual void parseAction(giada::m::recorder::action *a, int localFrame,
+ virtual void parseAction(giada::m::recorder::action* a, int localFrame,
int globalFrame, int quantize, bool mixerIsRunning) = 0;
/* rewind
virtual void receiveMidi(uint32_t msg);
+ /* allocBuffers
+ Mandatory method to allocate memory for internal buffers. Call it after the
+ object has been constructed. */
+
+ virtual bool allocBuffers();
+
/* ------------------------------------------------------------------------ */
- int index; // unique id
- int type; // midi or sample
- int status; // status: see const.h
- int key; // keyboard button
- float volume; // global volume
- float volume_i; // internal volume
- float volume_d; // delta volume (for envelope)
- bool mute_i; // internal mute
- bool mute_s; // previous mute status after being solo'd
- bool mute; // global mute
- bool solo;
- bool hasActions; // has something recorded
- bool readActions; // read what's recorded
- bool armed; // armed for recording
- int recStatus; // status of recordings (waiting, ending, ...)
- float *vChan; // virtual channel
- geChannel *guiChannel; // pointer to a gChannel object, part of the GUI
+ int index; // unique id
+ int type; // midi or sample
+ int status; // status: see const.h
+ int key; // keyboard button
+ float volume; // global volume
+ float volume_i; // internal volume
+ float volume_d; // delta volume (for envelope)
+ bool mute_i; // internal mute
+ bool mute_s; // previous mute status after being solo'd
+ bool mute; // global mute
+ bool solo;
+ bool hasActions; // has something recorded
+ bool readActions; // read what's recorded
+ bool armed; // armed for recording
+ int recStatus; // status of recordings (waiting, ending, ...)
+ float* vChan; // virtual channel
+ geChannel* guiChannel; // pointer to a gChannel object, part of the GUI
// TODO - midi structs, please
uint32_t midiOutLsolo;
#ifdef WITH_VST
- std::vector <Plugin *> plugins;
+ std::vector <Plugin*> plugins;
#endif
void setPan(float v);
float getPan();
+ void setPreviewMode(int m);
+ bool isPreview();
+
#ifdef WITH_VST
/* getPluginMidiEvents
* Return a reference to midiBuffer stack. This is available for any kind of
* channel, but it makes sense only for MIDI channels. */
- juce::MidiBuffer &getPluginMidiEvents();
+ juce::MidiBuffer& getPluginMidiEvents();
void clearMidiBuffer();
int midiTCminutes = 0;
int midiTChours = 0;
-#ifdef __linux__
+#ifdef G_OS_LINUX
kernelAudio::JackState jackStatePrev;
#endif
void init(int sampleRate, float midiTCfps)
{
midiTCrate = (sampleRate / midiTCfps) * 2; // stereo values
+ running = false;
+ bpm = G_DEFAULT_BPM;
+ bars = G_DEFAULT_BARS;
+ beats = G_DEFAULT_BEATS;
+ quantize = G_DEFAULT_QUANTIZE;
updateFrameBars();
}
/* -------------------------------------------------------------------------- */
-#ifdef __linux__
+#ifdef G_OS_LINUX
void recvJackSync()
{
if (soundDeviceIn < -1) soundDeviceIn = G_DEFAULT_SOUNDDEV_IN;
if (channelsOut < 0) channelsOut = 0;
if (channelsIn < 0) channelsIn = 0;
- if (buffersize < 8 || buffersize > 4096) buffersize = G_DEFAULT_BUFSIZE;
+ if (buffersize < G_MIN_BUF_SIZE || buffersize > G_MAX_BUF_SIZE) buffersize = G_DEFAULT_BUFSIZE;
if (delayComp < 0) delayComp = G_DEFAULT_DELAYCOMP;
if (midiPortOut < -1) midiPortOut = G_DEFAULT_MIDI_SYSTEM;
if (midiPortOut < -1) midiPortOut = G_DEFAULT_MIDI_PORT_OUT;
if (actionEditorW < 640) actionEditorW = 640;
if (actionEditorH < 176) actionEditorH = 176;
if (actionEditorZoom < 100) actionEditorZoom = 100;
- if (actionEditorGridVal < 0) actionEditorGridVal = 0;
+ if (actionEditorGridVal < 0 || actionEditorGridVal > G_MAX_GRID_VAL) actionEditorGridVal = 0;
if (actionEditorGridOn < 0) actionEditorGridOn = 0;
if (pianoRollH <= 0) pianoRollH = 422;
if (sampleEditorX < 0) sampleEditorX = 0;
if (sampleEditorY < 0) sampleEditorY = 0;
if (sampleEditorW < 500) sampleEditorW = 500;
if (sampleEditorH < 292) sampleEditorH = 292;
- if (sampleEditorGridVal < 0) sampleEditorGridVal = 0;
+ if (sampleEditorGridVal < 0 || sampleEditorGridVal > G_MAX_GRID_VAL) sampleEditorGridVal = 0;
if (sampleEditorGridOn < 0) sampleEditorGridOn = 0;
if (midiInputX < 0) midiInputX = 0;
if (midiInputY < 0) midiInputY = 0;
int sampleEditorY = 0;
int sampleEditorW = 640;
int sampleEditorH = 480;
-int sampleEditorGridVal = 1;
+int sampleEditorGridVal = 0;
int sampleEditorGridOn = false;
int midiInputX = 0;
/* -- version --------------------------------------------------------------- */
#define G_APP_NAME "Giada"
-#define G_VERSION_STR "0.14.1"
+#define G_VERSION_STR "0.14.2"
#define G_VERSION_MAJOR 0
#define G_VERSION_MINOR 14
-#define G_VERSION_PATCH 1
+#define G_VERSION_PATCH 2
#define CONF_FILENAME "giada.conf"
#define G_MIN_COLUMN_WIDTH 140
#define G_MAX_BOOST_DB 20.0f
#define G_MAX_PITCH 4.0f
+#define G_MAX_GRID_VAL 64
+#define G_MIN_BUF_SIZE 8
+#define G_MAX_BUF_SIZE 4096
#endif
#define G_DEFAULT_SOUNDDEV_OUT 0 // FIXME - please override with rtAudio::getDefaultDevice (or similar)
-#define G_DEFAULT_SOUNDDEV_IN -1 // no recording by default: input disabled
+#define G_DEFAULT_SOUNDDEV_IN -1 // no recording by default: input disabled
#define G_DEFAULT_MIDI_SYSTEM 0
#define G_DEFAULT_MIDI_PORT_IN -1
#define G_DEFAULT_MIDI_PORT_OUT -1
#define G_DEFAULT_SAMPLERATE 44100
#define G_DEFAULT_BUFSIZE 1024
#define G_DEFAULT_DELAYCOMP 0
-#define G_DEFAULT_VOL 1.0f
-#define G_DEFAULT_PITCH 1.0f
-#define G_DEFAULT_BOOST 1.0f
-#define G_DEFAULT_OUT_VOL 1.0f
-#define G_DEFAULT_IN_VOL 1.0f
-#define G_DEFAULT_CHANMODE SINGLE_BASIC
-#define G_DEFAULT_BPM 120.0f
-#define G_DEFAULT_BEATS 4
-#define G_DEFAULT_BARS 1
-#define G_DEFAULT_QUANTIZE 0 // quantizer off
+#define G_DEFAULT_BIT_DEPTH 32 // float
+#define G_DEFAULT_AUDIO_CHANS 2 // stereo for internal processing
+#define G_DEFAULT_VOL 1.0f
+#define G_DEFAULT_PITCH 1.0f
+#define G_DEFAULT_BOOST 1.0f
+#define G_DEFAULT_OUT_VOL 1.0f
+#define G_DEFAULT_IN_VOL 1.0f
+#define G_DEFAULT_CHANMODE SINGLE_BASIC
+#define G_DEFAULT_BPM 120.0f
+#define G_DEFAULT_BEATS 4
+#define G_DEFAULT_BARS 1
+#define G_DEFAULT_QUANTIZE 0 // quantizer off
#define G_DEFAULT_FADEOUT_STEP 0.01f // micro-fadeout speed
#define G_DEFAULT_COLUMN_WIDTH 380
-#define G_DEFAULT_PATCH_NAME "(default patch)"
+#define G_DEFAULT_PATCH_NAME "(default patch)"
#define G_DEFAULT_MIDI_INPUT_UI_W 300
#define G_DEFAULT_MIDI_INPUT_UI_H 350
+/* -- preview modes --------------------------------------------------------- */
+#define G_PREVIEW_NONE 0x00
+#define G_PREVIEW_NORMAL 0x01
+#define G_PREVIEW_LOOP 0x02
+
+
+
/* -- actions --------------------------------------------------------------- */
#define G_ACTION_KEYPRESS 0x01 // 0000 0001
#define G_ACTION_KEYREL 0x02 // 0000 0010
-/* -- mixerHandler signals -------------------------------------------------- */
-#define SAMPLE_LOADED_OK 0x01
-#define SAMPLE_LEFT_EMPTY 0x02
-#define SAMPLE_NOT_VALID 0x04
-#define SAMPLE_MULTICHANNEL 0x08
-#define SAMPLE_WRONG_BIT 0x10
-#define SAMPLE_WRONG_ENDIAN 0x20
-#define SAMPLE_WRONG_FORMAT 0x40
-#define SAMPLE_READ_ERROR 0x80
-#define SAMPLE_PATH_TOO_LONG 0x100
-
-/** FIXME - add to SAMPLE_ series those for when exporting */
+/* -- responses and return codes -------------------------------------------- */
+#define G_RES_ERR_PROCESSING -6
+#define G_RES_ERR_WRONG_DATA -5
+#define G_RES_ERR_NO_DATA -4
+#define G_RES_ERR_PATH_TOO_LONG -3
+#define G_RES_ERR_IO -2
+#define G_RES_ERR_MEMORY -1
+#define G_RES_ERR 0
+#define G_RES_OK 1
/* -------------------------------------------------------------------------- */
+void MidiChannel::preview(float *outBuffer)
+{
+ // No preview for MIDI channels (for now).
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
void MidiChannel::start(int frame, bool doQuantize, int quantize,
bool mixerIsRunning, bool forceStart, bool isUserGenerated)
{
midiOut = pch->midiOut;
midiOutChan = pch->midiOutChan;
- return SAMPLE_LOADED_OK; /// TODO - change name, it's meaningless here
+ return G_RES_OK;
}
void copy(const Channel *src, pthread_mutex_t *pluginMutex) override;
void clear() override;
void process(float *outBuffer, float *inBuffer) override;
+ void preview(float *outBuffer) override;
void start(int frame, bool doQuantize, int quantize, bool mixerIsRunning,
bool forceStart, bool isUserGenerated) override;
void kill(int frame) override;
json_t *jInitCommand;
json_array_foreach(jInitCommands, commandIndex, jInitCommand) {
- string indexStr = "init command " + gu_itoa(commandIndex);
+ string indexStr = "init command " + gu_toString(commandIndex);
if (!storager::checkObject(jInitCommand, indexStr.c_str()))
return 0;
/* lineInRec
Records from line in. */
-void lineInRec(float *inBuf, unsigned frame)
+void lineInRec(float* inBuf, unsigned frame)
{
if (!mh::hasArmedSampleChannels() || !kernelAudio::isInputEnabled() || !recording)
return;
/* ProcessLineIn
Computes line in peaks, plus handles "hear what you're playin'" thing. */
-void processLineIn(float *inBuf, unsigned frame)
+void processLineIn(float* inBuf, unsigned frame)
{
if (!kernelAudio::isInputEnabled())
return;
/* clearAllBuffers
Cleans up every buffer, both in Mixer and in channels. */
-void clearAllBuffers(float *outBuf, unsigned bufferSize)
+void clearAllBuffers(float* outBuf, unsigned bufferSize)
{
memset(outBuf, 0, sizeof(float) * bufferSize); // out
memset(vChanInToOut, 0, sizeof(float) * bufferSize); // inToOut vChan
/* renderMetronome
Generates metronome when needed and pastes it to the output buffer. */
-void renderMetronome(float *outBuf, unsigned frame)
+void renderMetronome(float* outBuf, unsigned frame)
{
if (tockPlay) {
outBuf[frame] += tock[tockTracker];
Final processing stage. Take each channel and process it (i.e. copy its
content to the output buffer). Process plugins too, if any. */
-void renderIO(float *outBuf, float *inBuf)
+void renderIO(float* outBuf, float* inBuf)
{
pthread_mutex_lock(&mutex_chans);
- for (unsigned k=0; k<channels.size(); k++)
- channels.at(k)->process(outBuf, inBuf);
+ for (Channel* channel : channels) {
+ channel->process(outBuf, inBuf);
+ channel->preview(outBuf);
+ }
pthread_mutex_unlock(&mutex_chans);
#ifdef WITH_VST
/* limitOutput
Applies a very dumb hard limiter. */
-void limitOutput(float *outBuf, unsigned frame)
+void limitOutput(float* outBuf, unsigned frame)
{
if (outBuf[frame] > 1.0f)
outBuf[frame] = 1.0f;
/* computePeak */
-void computePeak(float *outBuf, unsigned frame)
+void computePeak(float* outBuf, unsigned frame)
{
/* TODO it takes into account only left channel so far! */
if (outBuf[frame] > peakOut)
Last touches after the output has been rendered: apply inToOut if any, apply
output volume. */
-void finalizeOutput(float *outBuf, unsigned frame)
+void finalizeOutput(float* outBuf, unsigned frame)
{
/* merge vChanInToOut, if enabled */
/* -------------------------------------------------------------------------- */
-int masterPlay(void *_outBuf, void *_inBuf, unsigned bufferSize,
- double streamTime, RtAudioStreamStatus status, void *userData)
+int masterPlay(void* _outBuf, void* _inBuf, unsigned bufferSize,
+ double streamTime, RtAudioStreamStatus status, void* userData)
{
if (!ready)
return 0;
clock::recvJackSync();
#endif
- float *outBuf = (float*) _outBuf;
- float *inBuf = kernelAudio::isInputEnabled() ? (float*) _inBuf : nullptr;
+ float* outBuf = (float*) _outBuf;
+ float* inBuf = kernelAudio::isInputEnabled() ? (float*) _inBuf : nullptr;
bufferSize *= 2; // stereo
peakOut = 0.0f; // reset peak calculator
peakIn = 0.0f; // reset peak calculator
continue;
SampleChannel *ch = static_cast<SampleChannel*>(channels.at(i));
if (ch->armed)
- memcpy(ch->wave->data, vChanInput, clock::getTotalFrames() * sizeof(float));
+ memcpy(ch->wave->getData(), vChanInput, clock::getTotalFrames() * sizeof(float));
}
memset(vChanInput, 0, clock::getTotalFrames() * sizeof(float)); // clear vchan
}
#include "../utils/log.h"
#include "../glue/main.h"
#include "../glue/channel.h"
-#include "mixerHandler.h"
#include "kernelMidi.h"
#include "mixer.h"
#include "const.h"
#include "sampleChannel.h"
#include "midiChannel.h"
#include "wave.h"
+#include "waveManager.h"
+#include "mixerHandler.h"
using std::vector;
/* -------------------------------------------------------------------------- */
-bool uniqueSampleName(SampleChannel *ch, const string &name)
+bool uniqueSampleName(SampleChannel* ch, const string& name)
{
for (unsigned i=0; i<mixer::channels.size(); i++) {
if (ch == mixer::channels.at(i)) // skip itself
continue;
if (mixer::channels.at(i)->type != CHANNEL_SAMPLE)
continue;
- SampleChannel *other = (SampleChannel*) mixer::channels.at(i);
- if (other->wave != nullptr && name == other->wave->name)
+ SampleChannel* other = (SampleChannel*) mixer::channels.at(i);
+ if (other->wave != nullptr && name == other->wave->getName())
return false;
}
return true;
/* -------------------------------------------------------------------------- */
-Channel *addChannel(int type)
+Channel* addChannel(int type)
{
- Channel *ch;
+ Channel* ch;
int bufferSize = kernelAudio::getRealBufSize() * 2;
if (type == CHANNEL_SAMPLE)
else
ch = new MidiChannel(bufferSize);
+ if (!ch->allocBuffers()) {
+ delete ch;
+ return nullptr;
+ }
+
while (true) {
if (pthread_mutex_trylock(&mixer::mutex_chans) != 0)
continue;
/* -------------------------------------------------------------------------- */
-int deleteChannel(Channel *ch)
+int deleteChannel(Channel* ch)
{
int index = -1;
for (unsigned i=0; i<mixer::channels.size(); i++) {
if (mixer::channels.at(i)->type != CHANNEL_SAMPLE)
continue;
SampleChannel *ch = static_cast<SampleChannel*>(mixer::channels.at(i));
- if (ch->wave && ch->wave->isLogical)
+ if (ch->wave && ch->wave->isLogical())
return true;
}
return false;
if (mixer::channels.at(i)->type != CHANNEL_SAMPLE)
continue;
SampleChannel *ch = static_cast<SampleChannel*>(mixer::channels.at(i));
- if (ch->wave && ch->wave->isEdited)
+ if (ch->wave && ch->wave->isEdited())
return true;
}
return false;
{
int channelsReady = 0;
- for (unsigned i=0; i<mixer::channels.size(); i++) {
+ for (Channel* channel : mixer::channels) {
- if (!mixer::channels.at(i)->canInputRec())
+ if (!channel->canInputRec())
continue;
- SampleChannel *ch = (SampleChannel*) mixer::channels.at(i);
+ SampleChannel* ch = static_cast<SampleChannel*>(channel);
/* Allocate empty sample for the current channel. */
- if (!ch->allocEmpty(clock::getTotalFrames(), conf::samplerate, patch::lastTakeId))
- {
+ Wave* wave = nullptr;
+ int result = waveManager::createEmpty(clock::getTotalFrames(),
+ conf::samplerate, string("TAKE-" + gu_toString(patch::lastTakeId)), &wave);
+ if (result != G_RES_OK) {
gu_log("[startInputRec] unable to allocate new Wave in chan %d!\n",
ch->index);
continue;
}
- /* Increase lastTakeId until the sample name TAKE-[n] is unique */
+ ch->pushWave(wave, false); // false: don't generate name, we provide it
+
+ /* Increase lastTakeId until the sample name TAKE-[n] is unique. */
- while (!uniqueSampleName(ch, ch->wave->name)) {
+ while (!uniqueSampleName(ch, ch->wave->getName())) {
patch::lastTakeId++;
- ch->wave->name = "TAKE-" + gu_itoa(patch::lastTakeId);
+ ch->wave->setName("TAKE-" + gu_toString(patch::lastTakeId));
}
gu_log("[startInputRec] start input recs using chan %d with size %d "
json_t *jChannel;
json_array_foreach(jChannels, channelIndex, jChannel) {
- string channelIndexStr = "channel " + gu_itoa(channelIndex);
+ string channelIndexStr = "channel " + gu_toString(channelIndex);
if (!storager::checkObject(jChannel, channelIndexStr.c_str()))
return 0;
json_t *jColumn;
json_array_foreach(jColumns, columnIndex, jColumn) {
- string columnIndexStr = "column " + gu_itoa(columnIndex);
+ string columnIndexStr = "column " + gu_toString(columnIndex);
if (!storager::checkObject(jColumn, columnIndexStr.c_str()))
return 0;
/* Try to enable editor (i.e. plugin's UI) */
- if (plugin->getActiveEditor() != nullptr) {
- gu_log("[Plugin] plugin has an already active editor!\n");
- return;
- }
ui = plugin->createEditorIfNeeded();
- if (ui == nullptr) {
+ if (ui == nullptr)
gu_log("[Plugin] unable to create editor, the plugin might be GUI-less!\n");
- return;
- }
plugin->prepareToPlay(samplerate, buffersize);
#include <cmath>
#include <cstring>
+#include <cassert>
#include "../utils/log.h"
#include "../utils/fs.h"
#include "../utils/string.h"
-#include "sampleChannel.h"
#include "patch.h"
#include "const.h"
#include "conf.h"
#include "wave.h"
#include "pluginHost.h"
#include "waveFx.h"
+#include "waveManager.h"
#include "mixerHandler.h"
#include "kernelMidi.h"
#include "kernelAudio.h"
+#include "sampleChannel.h"
using std::string;
SampleChannel::SampleChannel(int bufferSize, bool inputMonitor)
: Channel (CHANNEL_SAMPLE, STATUS_EMPTY, bufferSize),
+ rsmp_state (nullptr),
+ pChan (nullptr),
+ vChanPreview (nullptr),
frameRewind (-1),
+ begin (0),
+ end (0),
boost (G_DEFAULT_BOOST),
pitch (G_DEFAULT_PITCH),
+ trackerPreview (0),
wave (nullptr),
tracker (0),
- begin (0),
- end (0),
mode (G_DEFAULT_CHANMODE),
qWait (false),
fadeinOn (false),
fadeoutVol (1.0f),
fadeoutTracker (0),
fadeoutStep (G_DEFAULT_FADEOUT_STEP),
- inputMonitor (inputMonitor),
- midiInReadActions(0x0),
- midiInPitch (0x0)
+ inputMonitor (inputMonitor),
+ midiInReadActions(0x0),
+ midiInPitch (0x0)
{
- rsmp_state = src_new(SRC_LINEAR, 2, nullptr);
- pChan = (float *) malloc(kernelAudio::getRealBufSize() * 2 * sizeof(float));
}
SampleChannel::~SampleChannel()
{
- if (wave)
+ if (wave != nullptr)
delete wave;
- src_delete(rsmp_state);
- free(pChan);
+ if (rsmp_state != nullptr)
+ src_delete(rsmp_state);
+ if (pChan != nullptr)
+ delete[] pChan;
+ if (vChanPreview != nullptr)
+ delete[] vChanPreview;
}
/* -------------------------------------------------------------------------- */
-void SampleChannel::copy(const Channel *_src, pthread_mutex_t *pluginMutex)
+bool SampleChannel::allocBuffers()
+{
+ if (!Channel::allocBuffers())
+ return false;
+
+ rsmp_state = src_new(SRC_LINEAR, 2, nullptr);
+ if (rsmp_state == nullptr) {
+ gu_log("[SampleChannel::allocBuffers] unable to alloc memory for SRC_STATE!\n");
+ return false;
+ }
+
+ pChan = new (std::nothrow) float[bufferSize];
+ if (pChan == nullptr) {
+ gu_log("[SampleChannel::allocBuffers] unable to alloc memory for pChan!\n");
+ return false;
+ }
+
+ vChanPreview = new (std::nothrow) float[bufferSize];
+ if (vChanPreview == nullptr) {
+ gu_log("[SampleChannel::allocBuffers] unable to alloc memory for vChanPreview!\n");
+ return false;
+ }
+
+ return true;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleChannel::copy(const Channel* _src, pthread_mutex_t* pluginMutex)
{
Channel::copy(_src, pluginMutex);
- SampleChannel *src = (SampleChannel *) _src;
+ const SampleChannel* src = static_cast<const SampleChannel*>(_src);
tracker = src->tracker;
begin = src->begin;
end = src->end;
fadeoutEnd = src->fadeoutEnd;
setPitch(src->pitch);
- if (src->wave) {
- Wave *w = new Wave(*src->wave); // invoke Wave's copy constructor
- pushWave(w);
- generateUniqueSampleName();
- }
+ if (src->wave)
+ pushWave(new Wave(*src->wave)); // invoke Wave's copy constructor
}
void SampleChannel::generateUniqueSampleName()
{
- string oldName = wave->name;
- int k = 1; // Start from k = 1, zero is too nerdy
- while (!mh::uniqueSampleName(this, wave->name)) {
- wave->updateName((oldName + "-" + gu_itoa(k)).c_str());
+ string oldName = wave->getName();
+ int k = 0;
+ while (!mh::uniqueSampleName(this, wave->getName())) {
+ wave->setName(oldName + "-" + gu_toString(k));
k++;
}
}
/** TODO - these memsets can be done only if status PLAY (if below),
* but it would require extra clearPChan calls when samples stop */
- std::memset(vChan, 0, sizeof(float) * bufferSize);
- std::memset(pChan, 0, sizeof(float) * bufferSize);
+ std::memset(vChan, 0, sizeof(float) * bufferSize);
+ std::memset(pChan, 0, sizeof(float) * bufferSize);
if (status & (STATUS_PLAY | STATUS_ENDING)) {
tracker = fillChan(vChan, tracker, 0);
/* -------------------------------------------------------------------------- */
-int SampleChannel::save(const char *path)
+int SampleChannel::save(const char* path)
{
- return wave->writeData(path);
+ return waveManager::save(wave, path);
}
/* -------------------------------------------------------------------------- */
-void SampleChannel::setBegin(int v)
+void SampleChannel::setBegin(int f)
{
- if (v < 0)
+ /* TODO - Opaque channel's count - everything in SampleChannel should be
+ frame-based, not sample-based. I.e. Remove * wave->getChannels() stuff. */
+
+ if (f < 0)
begin = 0;
else
- if (v > wave->size)
- begin = wave->size - 2;
+ if (f > wave->getSize())
+ begin = wave->getSize() * wave->getChannels();
else
- if (v >= end)
- begin = end - 2;
+ if (f >= end)
+ begin = end - wave->getChannels();
else
- begin = v;
+ begin = f * wave->getChannels();
+
tracker = begin;
+ trackerPreview = begin;
}
/* -------------------------------------------------------------------------- */
-void SampleChannel::setEnd(int v)
+void SampleChannel::setEnd(int f)
{
- if (v < 0)
- end = begin + 2;
+ /* TODO - Opaque channel's count - everything in SampleChannel should be
+ frame-based, not sample-based. I.e. Remove * wave->getChannels() stuff. */
+
+ if (f < 0)
+ end = begin + wave->getChannels();
else
- if (v > wave->size)
- end = wave->size;
+ if (f >= wave->getSize())
+ end = (wave->getSize() - 1) * wave->getChannels();
else
- if (v <= begin)
- end = begin + 2;
+ if (f <= begin)
+ end = begin + wave->getChannels();
else
- end = v;
+ end = f * wave->getChannels();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+int SampleChannel::getBegin()
+{
+ /* TODO - Opaque channel's count - everything in SampleChannel should be
+ frame-based, not sample-based. I.e. Remove / wave->getChannels() stuff. */
+
+ return begin / wave->getChannels();
+}
+
+
+int SampleChannel::getEnd()
+{
+ /* TODO - Opaque channel's count - everything in SampleChannel should be
+ frame-based, not sample-based. I.e. Remove / wave->getChannels() stuff. */
+
+ return end / wave->getChannels();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleChannel::setTrackerPreview(int f)
+{
+ /* TODO - Opaque channel's count - everything in SampleChannel should be
+ frame-based, not sample-based. I.e. Remove * wave->getChannels() stuff. */
+
+ trackerPreview = f * wave->getChannels();
+}
+
+
+int SampleChannel::getTrackerPreview() const
+{
+ /* TODO - Opaque channel's count - everything in SampleChannel should be
+ frame-based, not sample-based. I.e. Remove / wave->getChannels() stuff. */
+
+ return trackerPreview / wave->getChannels();
}
/* -------------------------------------------------------------------------- */
-void SampleChannel::parseAction(recorder::action *a, int localFrame,
+void SampleChannel::parseAction(recorder::action* a, int localFrame,
int globalFrame, int quantize, bool mixerIsRunning)
{
if (readActions == false)
int SampleChannel::getPosition()
{
+ /* TODO - Opaque channel's count - everything in SampleChannel should be
+ frame-based, not sample-based. I.e. Remove / wave->getChannels() stuff. */
+
if (status & ~(STATUS_EMPTY | STATUS_MISSING | STATUS_OFF)) // if is not (...)
- return tracker - begin;
+ return (tracker - begin) / wave->getChannels();
else
return -1;
}
/* -------------------------------------------------------------------------- */
+void SampleChannel::setOnEndPreviewCb(std::function<void()> f)
+{
+ onPreviewEnd = f;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
void SampleChannel::setFadeIn(bool internal)
{
if (internal) mute_i = false; // remove mute before fading in
/* -------------------------------------------------------------------------- */
+/* On reset, if frame > 0 and in play, fill again pChan to create something like
+this:
-/* on reset, if frame > 0 and in play, fill again pChan to create
- * something like this:
- *
- * |abcdefabcdefab*abcdefabcde|
- * [old data-----]*[new data--]
- *
- * */
+ |abcdefabcdefab*abcdefabcde|
+ [old data-----]*[new data--]
+
+*/
void SampleChannel::reset(int frame)
{
void SampleChannel::empty()
{
status = STATUS_OFF;
- if (wave) {
+ if (wave != nullptr) {
delete wave;
wave = nullptr;
}
/* -------------------------------------------------------------------------- */
-void SampleChannel::pushWave(Wave *w)
+void SampleChannel::pushWave(Wave* w, bool generateName)
{
+ sendMidiLplay(); // FIXME - why here?!?!
wave = w;
status = STATUS_OFF;
- sendMidiLplay(); // FIXME - why here?!?!
begin = 0;
- end = wave->size;
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-bool SampleChannel::allocEmpty(int frames, int samplerate, int takeId)
-{
- Wave *w = new Wave();
- if (!w->allocEmpty(frames, samplerate))
- return false;
-
- w->name = "TAKE-" + gu_itoa(takeId);
- w->pathfile = gu_getCurrentPath() + G_SLASH + w->name;
- wave = w;
- status = STATUS_OFF;
- begin = 0;
- end = wave->size;
-
- sendMidiLplay(); // FIXME - why here?!?!
-
- return true;
+ end = (wave->getSize() - 1) * wave->getChannels(); // TODO - Opaque channels' count
+ if (generateName)
+ generateUniqueSampleName();
}
/* -------------------------------------------------------------------------- */
-void SampleChannel::process(float *outBuffer, float *inBuffer)
+void SampleChannel::process(float* outBuffer, float* inBuffer)
{
/* If armed and inbuffer is not nullptr (i.e. input device available) and
input monitor is on, copy input buffer to vChan: this enables the input
/* -------------------------------------------------------------------------- */
+void SampleChannel::preview(float* outBuffer)
+{
+ if (previewMode == G_PREVIEW_NONE)
+ return;
+
+ std::memset(vChanPreview, 0, sizeof(float) * bufferSize);
+
+ /* If the tracker exceedes the end point and preview is looped, split the
+ rendering as in SampleChannel::reset(). */
+
+ if (trackerPreview + bufferSize >= end) {
+ int offset = end - trackerPreview;
+ trackerPreview = fillChan(vChanPreview, trackerPreview, 0, false);
+ trackerPreview = begin;
+ if (previewMode == G_PREVIEW_LOOP)
+ trackerPreview = fillChan(vChanPreview, begin, offset, false);
+ else
+ if (previewMode == G_PREVIEW_NORMAL) {
+ previewMode = G_PREVIEW_NONE;
+ if (onPreviewEnd)
+ onPreviewEnd();
+ }
+ }
+ else
+ trackerPreview = fillChan(vChanPreview, trackerPreview, 0, false);
+
+ for (int j=0; j<bufferSize; j+=2) {
+ outBuffer[j] += vChanPreview[j] * volume * calcPanning(0) * boost;
+ outBuffer[j+1] += vChanPreview[j+1] * volume * calcPanning(1) * boost;
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
void SampleChannel::kill(int frame)
{
if (wave != nullptr && status != STATUS_OFF) {
/* -------------------------------------------------------------------------- */
-int SampleChannel::load(const char *file, int samplerate, int rsmpQuality)
-{
- if (strcmp(file, "") == 0 || gu_isDir(file)) {
- gu_log("[SampleChannel] file not specified\n");
- return SAMPLE_LEFT_EMPTY;
- }
-
- if (strlen(file) > FILENAME_MAX)
- return SAMPLE_PATH_TOO_LONG;
-
- Wave *w = new Wave();
-
- if (!w->open(file)) {
- gu_log("[SampleChannel] %s: read error\n", file);
- delete w;
- return SAMPLE_READ_ERROR;
- }
-
- if (w->channels() > 2) {
- gu_log("[SampleChannel] %s: unsupported multichannel wave\n", file);
- delete w;
- return SAMPLE_MULTICHANNEL;
- }
-
- if (!w->readData()) {
- delete w;
- return SAMPLE_READ_ERROR;
- }
-
- if (w->channels() == 1) /** FIXME: error checking */
- wfx_monoToStereo(w);
-
- if (w->rate() != samplerate) {
- gu_log("[SampleChannel] input rate (%d) != system rate (%d), conversion needed\n",
- w->rate(), samplerate);
- w->resample(rsmpQuality, samplerate);
- }
-
- pushWave(w);
- generateUniqueSampleName();
-
- gu_log("[SampleChannel] %s loaded in channel %d\n", file, index);
- return SAMPLE_LOADED_OK;
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
int SampleChannel::readPatch(const string &basePath, int i,
pthread_mutex_t *pluginMutex, int samplerate, int rsmpQuality)
{
midiInPitch = pch->midiInPitch;
inputMonitor = pch->inputMonitor;
- int res = load((basePath + pch->samplePath).c_str(), samplerate, rsmpQuality);
- if (res == SAMPLE_LOADED_OK) {
+ Wave *w = nullptr;
+ int res = waveManager::create(basePath + pch->samplePath, &w);
+
+ if (res == G_RES_OK) {
+ pushWave(w);
setBegin(pch->begin);
setEnd (pch->end);
setPitch(pch->pitch);
}
else {
- if (res == SAMPLE_LEFT_EMPTY)
+ if (res == G_RES_ERR_NO_DATA)
status = STATUS_EMPTY;
else
- if (res == SAMPLE_READ_ERROR)
+ if (res == G_RES_ERR_IO)
status = STATUS_MISSING;
sendMidiLplay(); // FIXME - why sending MIDI lightning if sample status is wrong?
}
patch::channel_t *pch = &patch::channels.at(pchIndex);
if (wave != nullptr) {
- pch->samplePath = wave->pathfile;
+ pch->samplePath = wave->getPath();
if (isProject)
- pch->samplePath = gu_basename(wave->pathfile); // make it portable
+ pch->samplePath = gu_basename(wave->getPath()); // make it portable
}
else
pch->samplePath = "";
* end) */
if (start+bufferSize-offset <= end) {
- memcpy(dest+offset, wave->data+start, (bufferSize-offset)*sizeof(float));
+ memcpy(dest+offset, wave->getData()+start, (bufferSize-offset)*sizeof(float));
position = start+bufferSize-offset;
if (rewind)
frameRewind = -1;
* is smaller than 'dest' */
else {
- memcpy(dest+offset, wave->data+start, (end-start)*sizeof(float));
+ memcpy(dest+offset, wave->getData()+start, (end-start)*sizeof(float));
position = end;
if (rewind)
frameRewind = end-start+offset;
}
else {
- rsmp_data.data_in = wave->data+start; // source data
+ rsmp_data.data_in = wave->getData()+start; // source data
rsmp_data.input_frames = (end-start)/2; // how many readable bytes
rsmp_data.data_out = dest+offset; // destination (processed data)
rsmp_data.output_frames = (bufferSize-offset)/2; // how many bytes to process
#define G_SAMPLE_CHANNEL_H
+#include <functional>
#include <samplerate.h>
#include "channel.h"
/* rsmp_state, rsmp_data
* structs from libsamplerate */
- SRC_STATE *rsmp_state;
+ SRC_STATE* rsmp_state;
SRC_DATA rsmp_data;
/* pChan
Extra virtual channel for processing resampled data. */
- float *pChan;
+ float* pChan;
+
+ /* pChan
+ Extra virtual channel for audio preview. */
+
+ float* vChanPreview;
/* frameRewind
Exact frame in which a rewind occurs. */
int frameRewind;
+ int begin;
+ int end;
float boost;
float pitch;
+ int trackerPreview; // chan position for audio preview
+
+ /* onPreviewEnd
+ A callback fired when audio preview ends. */
+
+ std::function<void()> onPreviewEnd;
/* fillChan
- * copy from wave to *dest and resample data from wave, if necessary.
- * Start to fill pChan from byte 'offset'. If rewind=false don't
- * rewind internal tracker. Returns new sample position, in frames */
+ Fills 'dest' buffer at point 'offset' with wave data taken from 'start'. If
+ rewind=false don't rewind internal tracker. Returns new sample position,
+ in frames. It resamples data if pitch != 1.0f. */
- int fillChan(float *dest, int start, int offset, bool rewind=true);
+ int fillChan(float* dest, int start, int offset, bool rewind=true);
/* clearChan
* set data to zero from start to bufferSize-1. */
- void clearChan(float *dest, int start);
+ void clearChan(float* dest, int start);
/* calcFadeoutStep
* how many frames are left before the end of the sample? Is there
SampleChannel(int bufferSize, bool inputMonitor);
~SampleChannel();
- void copy(const Channel *src, pthread_mutex_t *pluginMutex) override;
+ void copy(const Channel* src, pthread_mutex_t* pluginMutex) override;
void clear() override;
- void process(float *outBuffer, float *inBuffer) override;
+ void process(float* outBuffer, float* inBuffer) override;
+ void preview(float* outBuffer) override;
void start(int frame, bool doQuantize, int quantize, bool mixerIsRunning,
bool forceStart, bool isUserGenerated) override;
void kill(int frame) override;
void rewind() override;
void setMute(bool internal) override;
void unsetMute(bool internal) override;
- int readPatch(const std::string &basePath, int i, pthread_mutex_t *pluginMutex,
+ int readPatch(const std::string& basePath, int i, pthread_mutex_t* pluginMutex,
int samplerate, int rsmpQuality) override;
int writePatch(int i, bool isProject) override;
void quantize(int index, int localFrame) override;
void onZero(int frame, bool recsStopOnChanHalt) override;
void onBar(int frame) override;
- void parseAction(giada::m::recorder::action *a, int localFrame, int globalFrame,
+ void parseAction(giada::m::recorder::action* a, int localFrame, int globalFrame,
int quantize, bool mixerIsRunning) override;
bool canInputRec() override;
-
- int load(const char *file, int samplerate, int rsmpQuality);
+ bool allocBuffers() override;
void reset(int frame);
* prepare channel for fade, mixer will take care of the process
* during master play. */
- void setFadeIn (bool internal);
- void setFadeOut (int actionPostFadeout);
- void setXFade (int frame);
+ void setFadeIn(bool internal);
+ void setFadeOut(int actionPostFadeout);
+ void setXFade(int frame);
/* pushWave
- * add a new wave to an existing channel. */
+ Adds a new wave to an existing channel. It also generates a unique name
+ on request. */
- void pushWave(Wave *w);
+ void pushWave(Wave* w, bool generateName=true);
/* getPosition
* returns the position of an active sample. If EMPTY o MISSING
void sum(int frame, bool running);
- /* setPitch
- * updates the pitch value and chanStart+chanEnd accordingly. */
void setPitch(float v);
float getPitch();
-
- /* setStart/end
- * change begin/end read points in sample. */
-
- void setBegin(int v);
- void setEnd (int v);
+ void setBegin(int f);
+ int getBegin();
+ void setEnd(int f);
+ int getEnd();
+ void setTrackerPreview(int f);
+ int getTrackerPreview() const;
/* save
* save sample to file. */
- int save(const char *path);
+ int save(const char* path);
/* hardStop
* stop the channel immediately, no further checks. */
void hardStop(int frame);
- /* allocEmpty
- * alloc an empty wave used in input recordings. */
-
- bool allocEmpty(int frames, int samplerate, int takeId);
-
/* setReadActions
* if enabled (v == true), recorder will read actions from this channel. If
* recsStopOnChanHalt == true, stop reading actions right away. */
void setBoost(float v);
float getBoost();
+ void setOnEndPreviewCb(std::function<void()> f);
+
/* ------------------------------------------------------------------------ */
- Wave *wave;
- int tracker; // chan position
- int begin;
- int end;
- int mode; // mode: see const.h
- bool qWait; // quantizer wait
- bool fadeinOn;
- float fadeinVol;
- bool fadeoutOn;
- float fadeoutVol; // fadeout volume
- int fadeoutTracker; // tracker fadeout, xfade only
- float fadeoutStep; // fadeout decrease
- int fadeoutType; // xfade or fadeout
- int fadeoutEnd; // what to do when fadeout ends
- bool inputMonitor;
+ Wave* wave;
+ int tracker; // chan position
+ int mode; // mode: see const.h
+ bool qWait; // quantizer wait
+ bool fadeinOn;
+ float fadeinVol;
+ bool fadeoutOn;
+ float fadeoutVol; // fadeout volume
+ int fadeoutTracker; // tracker fadeout, xfade only
+ float fadeoutStep; // fadeout decrease
+ int fadeoutType; // xfade or fadeout
+ int fadeoutEnd; // what to do when fadeout ends
+ bool inputMonitor;
/* midi stuff */
* -------------------------------------------------------------------------- */
-#include <cstdio>
-#include <cstdlib>
+#include <cassert>
#include <cstring> // memcpy
-#include <cmath>
-#include <samplerate.h>
#include "../utils/fs.h"
#include "../utils/log.h"
-#include "init.h"
#include "const.h"
#include "wave.h"
Wave::Wave()
-: data (nullptr),
- size (0),
- isLogical(0),
- isEdited (0) {}
+: m_data (nullptr),
+ m_size (0),
+ m_logical(0),
+ m_edited (0) {}
/* -------------------------------------------------------------------------- */
-Wave::~Wave()
+Wave::Wave(float* data, int size, int channels, int rate, int bits,
+ const std::string& path)
+: m_data (data),
+ m_size (size),
+ m_channels(channels),
+ m_rate (rate),
+ m_bits (bits),
+ m_logical (false),
+ m_edited (false),
+ m_path (path),
+ m_name (gu_stripExt(gu_basename(path)))
{
- clear();
-}
+}
/* -------------------------------------------------------------------------- */
-Wave::Wave(const Wave &other)
-: data (nullptr),
- size (0),
- isLogical(false),
- isEdited (false)
+Wave::~Wave()
{
- size = other.size;
- data = new float[size];
- memcpy(data, other.data, size * sizeof(float));
- memcpy(&inHeader, &other.inHeader, sizeof(other.inHeader));
- pathfile = other.pathfile;
- name = other.name;
- isLogical = true;
+ clear();
}
+
/* -------------------------------------------------------------------------- */
-int Wave::open(const char *f)
+Wave::Wave(const Wave& other)
+: m_data (nullptr),
+ m_size (other.m_size),
+ m_channels(other.m_channels),
+ m_rate (other.m_rate),
+ m_bits (other.m_bits),
+ m_logical (true), // a cloned wave does not exist on disk
+ m_edited (false),
+ m_path (other.m_path),
+ m_name (other.m_name)
{
- pathfile = f;
- name = gu_stripExt(gu_basename(f));
- fileIn = sf_open(f, SFM_READ, &inHeader);
-
- if (fileIn == nullptr) {
- gu_log("[wave] unable to read %s. %s\n", f, sf_strerror(fileIn));
- pathfile = "";
- name = "";
- return 0;
- }
-
- isLogical = false;
- isEdited = false;
-
- return 1;
+ m_data = new float[m_size];
+ memcpy(m_data, other.m_data, m_size * sizeof(float));
}
/* -------------------------------------------------------------------------- */
-/* how to read and write with libsndfile:
- *
- * a frame consists of all items (samples) that belong to the same
- * point in time. So in each frame there are as many items as there
- * are channels.
- *
- * Quindi:
- * frame = [item, item, ...]
- * In pratica:
- * frame1 = [itemLeft, itemRight]
- * frame2 = [itemLeft, itemRight]
- * ...
- */
-
-int Wave::readData()
+
+void Wave::clear()
{
- size = inHeader.frames * inHeader.channels;
- data = (float *) malloc(size * sizeof(float));
- if (data == nullptr) {
- gu_log("[wave] unable to allocate memory\n");
- return 0;
- }
-
- if (sf_read_float(fileIn, data, size) != size)
- gu_log("[wave] warning: incomplete read!\n");
-
- sf_close(fileIn);
- return 1;
+ free();
+ m_path = "";
+ m_size = 0;
}
/* -------------------------------------------------------------------------- */
-int Wave::writeData(const char *f)
+void Wave::free()
{
- /* prepare the header for output file */
-
- outHeader.samplerate = inHeader.samplerate;
- outHeader.channels = inHeader.channels;
- outHeader.format = inHeader.format;
-
- fileOut = sf_open(f, SFM_WRITE, &outHeader);
- if (fileOut == nullptr) {
- gu_log("[wave] unable to open %s for exporting\n", f);
- return 0;
- }
-
- int out = sf_write_float(fileOut, data, size);
- if (out != (int) size) {
- gu_log("[wave] error while exporting %s! %s\n", f, sf_strerror(fileOut));
- return 0;
- }
-
- isLogical = false;
- isEdited = false;
- sf_close(fileOut);
- return 1;
+ if (m_data == nullptr)
+ return;
+ delete[] m_data;
+ m_data = nullptr;
}
/* -------------------------------------------------------------------------- */
-void Wave::clear()
+string Wave::getBasename(bool ext) const
{
- if (data != nullptr) {
- free(data);
- data = nullptr;
- pathfile = "";
- size = 0;
- }
+ return ext ? gu_basename(m_path) : gu_stripExt(gu_basename(m_path));
}
/* -------------------------------------------------------------------------- */
-int Wave::allocEmpty(unsigned __size, unsigned samplerate)
+void Wave::setName(const string& n)
{
- /* the caller must pass a __size for stereo values */
-
- /// FIXME - this way if malloc fails size becomes wrong
- size = __size;
- data = (float *) malloc(size * sizeof(float));
- if (data == nullptr) {
- gu_log("[wave] unable to allocate memory\n");
- return 0;
- }
+ string ext = gu_getExt(m_path);
+ m_name = gu_stripExt(gu_basename(n));
+ m_path = gu_dirname(m_path) + G_SLASH + m_name + "." + ext;
+ m_logical = true;
- memset(data, 0, sizeof(float) * size); /// FIXME - is it useful?
-
- inHeader.samplerate = samplerate;
- inHeader.channels = 2;
- inHeader.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT; // wave only
-
- isLogical = true;
- return 1;
+ /* A wave with updated m_name must become logical, since the underlying file
+ does not exist yet. */
}
/* -------------------------------------------------------------------------- */
-int Wave::resample(int quality, int newRate)
-{
- float ratio = newRate / (float) inHeader.samplerate;
- int newSize = ceil(size * ratio);
- if (newSize % 2 != 0) // libsndfile goes crazy with odd size in case of saving
- newSize++;
-
- float *tmp = (float *) malloc(newSize * sizeof(float));
- if (!tmp) {
- gu_log("[wave] unable to allocate memory for resampling\n");
- return -1;
- }
-
- SRC_DATA src_data;
- src_data.data_in = data;
- src_data.input_frames = size/2; // in frames, i.e. /2 (stereo)
- src_data.data_out = tmp;
- src_data.output_frames = newSize/2; // in frames, i.e. /2 (stereo)
- src_data.src_ratio = ratio;
-
- gu_log("[wave] resampling: new size=%d (%d frames)\n", newSize, newSize/2);
-
- int ret = src_simple(&src_data, quality, 2);
- if (ret != 0) {
- gu_log("[wave] resampling error: %s\n", src_strerror(ret));
- return 0;
- }
-
- free(data);
- data = tmp;
- size = newSize;
- inHeader.samplerate = newRate;
- return 1;
-}
+int Wave::getRate() const { return m_rate; }
+int Wave::getChannels() const { return m_channels; }
+std::string Wave::getPath() const { return m_path; }
+std::string Wave::getName() const { return m_name; }
+float* Wave::getData() const { return m_data; }
+int Wave::getSize() const { return m_size / m_channels; }
+int Wave::getBits() const { return m_bits; }
+bool Wave::isLogical() const { return m_logical; }
+bool Wave::isEdited() const { return m_edited; }
/* -------------------------------------------------------------------------- */
-string Wave::basename(bool ext) const
+int Wave::getDuration() const
{
- return ext ? gu_basename(pathfile) : gu_stripExt(gu_basename(pathfile));
-}
-
-string Wave::extension() const
-{
- return gu_getExt(pathfile);
+ return m_size / m_channels / m_rate;
}
/* -------------------------------------------------------------------------- */
-void Wave::updateName(const char *n)
+float* Wave::getFrame(int f) const
{
- string ext = gu_getExt(pathfile);
- name = gu_stripExt(gu_basename(n));
- pathfile = gu_dirname(pathfile) + G_SLASH + name + "." + ext;
- isLogical = true;
+ assert(f >= 0);
+ assert(f < getSize());
- /* a wave with updated name must become logical, since the underlying
- * file does not exist yet. */
+ f *= m_channels; // convert frame to sample
+ return m_data + f; // i.e. a pointer to m_data[f]
}
/* -------------------------------------------------------------------------- */
-int Wave::rate () { return inHeader.samplerate; }
-int Wave::channels() { return inHeader.channels; }
-int Wave::frames () { return inHeader.frames; }
+void Wave::setRate(int v) { m_rate = v; }
+void Wave::setChannels(int v) { m_channels = v; }
+void Wave::setPath(const string& p) { m_path = p; }
+void Wave::setLogical(bool l) { m_logical = l; }
+void Wave::setEdited(bool e) { m_edited = e; }
/* -------------------------------------------------------------------------- */
-void Wave::rate (int v) { inHeader.samplerate = v; }
-void Wave::channels(int v) { inHeader.channels = v; }
-void Wave::frames (int v) { inHeader.frames = v; }
+void Wave::setData(float* d, int size)
+{
+ m_data = d;
+ m_size = size;
+}
#include <sndfile.h>
#include <string>
+#include "const.h"
class Wave
{
private:
- SNDFILE *fileIn;
- SNDFILE *fileOut;
- SF_INFO inHeader;
- SF_INFO outHeader;
+ float* m_data;
+ int m_size; // Wave size in bytes (size in stereo: size / 2)
+ int m_channels;
+ int m_rate;
+ int m_bits;
+ bool m_logical; // memory only (a take)
+ bool m_edited; // edited via editor
+
+ std::string m_path; // E.g. /path/to/my/sample.wav
+ std::string m_name; // Sample name (can be changed)
public:
Wave();
+ Wave(float* data, int size, int channels, int rate, int bits, const std::string& path);
~Wave();
- Wave(const Wave &other);
+ Wave(const Wave& other);
- std::string pathfile; // full path + sample name
- std::string name; // sample name (changeable)
+ void setRate(int v);
+ void setChannels(int v);
+ void setPath(const std::string& p);
+ void setName(const std::string& p);
+ void setData(float* data, int size);
+ void setLogical(bool l);
+ void setEdited(bool e);
- float *data;
- int size; // wave size (size in stereo: size / 2)
- bool isLogical; // memory only (a take)
- bool isEdited; // edited via editor
+ std::string getBasename(bool ext=false) const;
+ int getRate() const;
+ int getChannels() const;
+ std::string getPath() const;
+ std::string getName() const;
+ int getBits() const;
+ float* getData() const;
+ int getSize() const; // with channels count
+ int getDuration() const;
+ bool isLogical() const;
+ bool isEdited() const;
- int rate ();
- int channels();
- int frames ();
- void rate (int v);
- void channels(int v);
- void frames (int v);
+ /* clear
+ Resets Wave to init state. */
- std::string basename(bool ext=false) const;
- std::string extension() const;
+ void clear();
- void updateName(const char *n);
- int open (const char *f);
- int readData ();
- int writeData (const char *f);
- void clear ();
+ /* free
+ Frees memory, leaving everything else untouched. */
- /* allocEmpty
- * alloc an empty waveform. */
+ void free();
- int allocEmpty(unsigned size, unsigned samplerate);
+ /* getFrame
+ Given a frame 'f', returns a pointer to it. This is useful for digging inside
+ a frame, i.e. parsing each channel. How to use it:
- /* resample
- * simple algorithm for one-shot resampling. */
+ float* frame = w->getFrame(40);
+ for (int i=0; i<w->getChannels(); i++)
+ ... frame[i] ...
+ */
+
+ float* getFrame(int f) const;
- int resample(int quality, int newRate);
};
#endif
*
* Giada - Your Hardcore Loopmachine
*
- * waveFx
- *
* -----------------------------------------------------------------------------
*
* Copyright (C) 2010-2017 Giovanni A. Zuliani | Monocasual
#include "waveFx.h"
-float wfx_normalizeSoft(Wave *w)
+namespace giada {
+namespace m {
+namespace wfx
+{
+namespace
+{
+void fadeFrame(Wave* w, int i, float val)
+{
+ float* frame = w->getFrame(i);
+ for (int j=0; j<w->getChannels(); j++)
+ frame[j] *= val;
+}
+}; // {anonymous}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+float normalizeSoft(Wave *w)
{
float peak = 0.0f;
float abs = 0.0f;
- for (int i=0; i<w->size; i++) { // i++: both L and R samples
- abs = fabs(w->data[i]);
+ for (int i=0; i<w->getSize(); i++) {
+ float* frame = w->getFrame(i);
+ for (int j=0; j<w->getChannels(); j++) // Find highest value in any channel
+ abs = fabs(frame[j]);
if (abs > peak)
peak = abs;
}
/* -------------------------------------------------------------------------- */
-bool wfx_monoToStereo(Wave *w)
+int monoToStereo(Wave* w)
{
- unsigned newSize = w->size * 2;
- float *dataNew = (float *) malloc(newSize * sizeof(float));
- if (dataNew == nullptr) {
- gu_log("[wfx] unable to allocate memory for mono>stereo conversion\n");
- return 0;
+ if (w->getChannels() >= G_DEFAULT_AUDIO_CHANS)
+ return G_RES_OK;
+
+ unsigned newSize = w->getSize() * G_DEFAULT_AUDIO_CHANS;
+
+ float* newData = new (std::nothrow) float[newSize];
+ if (newData == nullptr) {
+ gu_log("[wfx::monoToStereo] unable to allocate memory!\n");
+ return G_RES_ERR_MEMORY;
}
- for (int i=0, j=0; i<w->size; i++) {
- dataNew[j] = w->data[i];
- dataNew[j+1] = w->data[i];
- j+=2;
+ for (int i=0, k=0; i<w->getSize(); i++, k+=G_DEFAULT_AUDIO_CHANS) {
+ float* frame = w->getFrame(i);
+ for (int j=0; j<G_DEFAULT_AUDIO_CHANS; j++)
+ newData[k+j] = frame[j];
}
- free(w->data);
- w->data = dataNew;
- w->size = newSize;
- w->frames(w->frames()*2);
- w->channels(2);
+ w->free();
+ w->setData(newData, newSize);
+ w->setChannels(G_DEFAULT_AUDIO_CHANS);
- return 1;
+ return G_RES_OK;
}
/* -------------------------------------------------------------------------- */
-void wfx_silence(Wave *w, int a, int b)
+void silence(Wave* w, int a, int b)
{
- /* stereo values */
- a = a * 2;
- b = b * 2;
+ gu_log("[wfx::silence] silencing from %d to %d\n", a, b);
- gu_log("[wfx] silencing from %d to %d\n", a, b);
-
- for (int i=a; i<b; i+=2) {
- w->data[i] = 0.0f;
- w->data[i+1] = 0.0f;
+ for (int i=a; i<b; i++) {
+ float* frame = w->getFrame(i);
+ for (int j=0; j<w->getChannels(); j++)
+ frame[j] = 0.0f;
}
- w->isEdited = true;
+ w->setEdited(true);
return;
}
/* -------------------------------------------------------------------------- */
-int wfx_cut(Wave *w, int a, int b)
+int cut(Wave* w, int a, int b)
{
- a = a * 2;
- b = b * 2;
-
if (a < 0) a = 0;
- if (b > w->size) b = w->size;
+ if (b > w->getSize()) b = w->getSize();
- /* create a new temp wave and copy there the original one, skipping
- * the a-b range */
+ /* Create a new temp wave and copy there the original one, skipping the a-b
+ range. */
- unsigned newSize = w->size-(b-a);
- float *temp = (float *) malloc(newSize * sizeof(float));
- if (temp == nullptr) {
- gu_log("[wfx] unable to allocate memory for cutting\n");
- return 0;
+ unsigned newSize = (w->getSize() - (b - a)) * w->getChannels();
+ float* newData = new (std::nothrow) float[newSize];
+ if (newData == nullptr) {
+ gu_log("[wfx::cut] unable to allocate memory!\n");
+ return G_RES_ERR_MEMORY;
}
- gu_log("[wfx] cutting from %d to %d, new size=%d (video=%d)\n",
- a, b, newSize, newSize/2);
+ gu_log("[wfx::cut] cutting from %d to %d\n", a, b);
- for (int i=0, k=0; i<w->size; i++) {
- if (i < a || i >= b) { // left margin always included, in order to keep
- temp[k] = w->data[i]; // the stereo pair
- k++;
+ for (int i=0, k=0; i<w->getSize(); i++) {
+ if (i < a || i >= b) {
+ float* frame = w->getFrame(i);
+ for (int j=0; j<w->getChannels(); j++)
+ newData[k+j] = frame[j];
+ k += w->getChannels();
}
}
- free(w->data);
- w->data = temp;
- w->size = newSize;
- //w->inHeader.frames -= b-a;
- w->frames(w->frames() - b - a);
- w->isEdited = true;
-
- gu_log("[wfx] cutting done\n");
+ w->free();
+ w->setData(newData, newSize);
+ w->setEdited(true);
- return 1;
+ return G_RES_OK;
}
/* -------------------------------------------------------------------------- */
-int wfx_trim(Wave *w, int a, int b)
+int trim(Wave* w, int a, int b)
{
- a = a * 2;
- b = b * 2;
-
if (a < 0) a = 0;
- if (b > w->size) b = w->size;
+ if (b > w->getSize()) b = w->getSize();
- int newSize = b - a;
- float *temp = (float *) malloc(newSize * sizeof(float));
- if (temp == nullptr) {
+ int newSize = (b - a) * w->getChannels();
+ float* newData = new (std::nothrow) float[newSize];
+ if (newData == nullptr) {
gu_log("[wfx] unable to allocate memory for trimming\n");
- return 0;
+ return G_RES_ERR_MEMORY;
}
- gu_log("[wfx] trimming from %d to %d (area = %d)\n", a, b, b-a);
+ gu_log("[wfx::trim] trimming from %d to %d (area = %d)\n", a, b, b-a);
- for (int i=a, k=0; i<b; i++, k++)
- temp[k] = w->data[i];
+ for (int i=a, k=0; i<b; i++, k+=w->getChannels()) {
+ float* frame = w->getFrame(i);
+ for (int j=0; j<w->getChannels(); j++)
+ newData[k+j] = frame[j];
+ }
- free(w->data);
- w->data = temp;
- w->size = newSize;
- //w->inHeader.frames = b-a;
- w->frames(b - a);
- w->isEdited = true;
+ w->free();
+ w->setData(newData, newSize);
+ w->setEdited(true);
- return 1;
+ return G_RES_OK;
}
/* -------------------------------------------------------------------------- */
-void wfx_fade(Wave *w, int a, int b, int type)
+void fade(Wave* w, int a, int b, int type)
{
- float m = type == 0 ? 0.0f : 1.0f;
- float d = 1.0f/(float)(b-a);
- if (type == 1)
- d = -d;
-
- a *= 2;
- b *= 2;
-
- for (int i=a; i<b; i+=2) {
- w->data[i] *= m;
- w->data[i+1] *= m;
- m += d;
- }
+ gu_log("[wfx::fade] fade from %d to %d (range = %d)\n", a, b, b-a);
- w->isEdited = true;
+ float m = 0.0f;
+ float d = 1.0f / (float) (b - a);
+
+ if (type == FADE_IN)
+ for (int i=a; i<=b; i++, m+=d)
+ fadeFrame(w, i, m);
+ else
+ for (int i=b; i>=a; i--, m+=d)
+ fadeFrame(w, i, m);
+
+ w->setEdited(true);
}
/* -------------------------------------------------------------------------- */
-void wfx_smooth(Wave *w, int a, int b)
+void smooth(Wave* w, int a, int b)
{
- int d = 32; // 64 if stereo data
-
- /* do nothing if fade edges (both of 32 samples) are > than selected
- * portion of wave. d*2 => count both edges, (b-a)*2 => stereo
- * values. */
+ /* Do nothing if fade edges (both of SMOOTH_SIZE samples) are > than selected
+ portion of wave. SMOOTH_SIZE*2 to count both edges. */
- if (d*2 > (b-a)*2) {
- gu_log("[WFX] selection is too small, nothing to do\n");
+ if (SMOOTH_SIZE*2 > (b-a)) {
+ gu_log("[wfx::smooth] selection is too small, nothing to do\n");
return;
}
- wfx_fade(w, a, a+d, 0);
- wfx_fade(w, b-d, b, 1);
+ fade(w, a, a+SMOOTH_SIZE, FADE_IN);
+ fade(w, b-SMOOTH_SIZE, b, FADE_OUT);
+
+ w->setEdited(true);
}
+
+}}}; // giada::m::wfx::
\ No newline at end of file
*
* Giada - Your Hardcore Loopmachine
*
- * waveFx
- *
* -----------------------------------------------------------------------------
*
* Copyright (C) 2010-2017 Giovanni A. Zuliani | Monocasual
class Wave;
-/* normalizeSoft
- * normalize the wave by returning the dB value for the boost volume. It
- * doesn't deal with data in memory. */
-
-float wfx_normalizeSoft(Wave *w);
-
-bool wfx_monoToStereo(Wave *w);
+namespace giada {
+namespace m {
+namespace wfx
+{
+static const int FADE_IN = 0;
+static const int FADE_OUT = 1;
+static const int SMOOTH_SIZE = 32;
-void wfx_silence(Wave *w, int a, int b);
+/* normalizeSoft
+Normalizes the wave by returning the dB value for the boost volume. */
-int wfx_cut(Wave *w, int a, int b);
+float normalizeSoft(Wave* w);
-int wfx_trim(Wave *w, int a, int b);
+int monoToStereo(Wave* w);
+void silence(Wave* w, int a, int b);
+int cut(Wave* w, int a, int b);
+int trim(Wave* w, int a, int b);
/* fade
* fade in or fade out selection. Fade In = type 0, Fade Out = type 1 */
-void wfx_fade(Wave *w, int a, int b, int type);
+void fade(Wave* w, int a, int b, int type);
/* smooth
* smooth edges of selection. */
-void wfx_smooth(Wave *w, int a, int b);
-
+void smooth(Wave* w, int a, int b);
+}}}; // giada::m::wfx::
#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2017 Giovanni A. Zuliani | Monocasual
+ *
+ * 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 <cmath>
+#include <sndfile.h>
+#include <samplerate.h>
+#include "../utils/log.h"
+#include "../utils/fs.h"
+#include "const.h"
+#include "wave.h"
+#include "waveFx.h"
+#include "waveManager.h"
+
+
+using std::string;
+
+
+namespace giada {
+namespace m {
+namespace waveManager
+{
+namespace
+{
+int getBits(SF_INFO& header)
+{
+ if (header.format & SF_FORMAT_PCM_S8)
+ return 8;
+ else if (header.format & SF_FORMAT_PCM_16)
+ return 16;
+ else if (header.format & SF_FORMAT_PCM_24)
+ return 24;
+ else if (header.format & SF_FORMAT_PCM_32)
+ return 32;
+ else if (header.format & SF_FORMAT_PCM_U8)
+ return 8;
+ else if (header.format & SF_FORMAT_FLOAT)
+ return 32;
+ else if (header.format & SF_FORMAT_DOUBLE)
+ return 64;
+ return 0;
+}
+}; // {anonymous}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+int create(const string& path, Wave** out)
+{
+ if (path == "" || gu_isDir(path)) {
+ gu_log("[waveManager::create] malformed path (was '%s')\n", path.c_str());
+ return G_RES_ERR_NO_DATA;
+ }
+
+ if (path.size() > FILENAME_MAX)
+ return G_RES_ERR_PATH_TOO_LONG;
+
+ SF_INFO header;
+ SNDFILE* fileIn = sf_open(path.c_str(), SFM_READ, &header);
+
+ if (fileIn == nullptr) {
+ gu_log("[waveManager::create] unable to read %s. %s\n", path.c_str(), sf_strerror(fileIn));
+ return G_RES_ERR_IO;
+ }
+
+ if (header.channels > 2) {
+ gu_log("[waveManager::create] unsupported multi-channel sample\n");
+ return G_RES_ERR_WRONG_DATA;
+ }
+
+ /* Libsndfile's frame structure:
+
+ frame1 = [leftChannel, rightChannel]
+ frame2 = [leftChannel, rightChannel]
+ ... */
+
+ int size = header.frames * header.channels;
+ float* data = new (std::nothrow) float[size];
+ if (data == nullptr) {
+ gu_log("[waveManager::create] unable to allocate memory\n");
+ return G_RES_ERR_MEMORY;
+ }
+
+ if (sf_read_float(fileIn, data, size) != size)
+ gu_log("[waveManager::create] warning: incomplete read!\n");
+
+ sf_close(fileIn);
+
+ Wave* wave = new Wave(data, size, header.channels, header.samplerate,
+ getBits(header), path);
+
+ if (header.channels == 1 && !wfx::monoToStereo(wave)) {
+ delete wave;
+ return G_RES_ERR_PROCESSING;
+ }
+
+ *out = wave;
+
+ gu_log("[waveManager::create] new Wave created, %d frames\n", wave->getSize());
+
+ return G_RES_OK;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+int createEmpty(int size, int samplerate, const string& name, Wave** out)
+{
+ float* data = new (std::nothrow) float[size];
+ if (data == nullptr) {
+ gu_log("[waveManager::createEmpty] unable to allocate memory\n");
+ return G_RES_ERR_MEMORY;
+ }
+
+ Wave *wave = new Wave(data, size, 2, samplerate, G_DEFAULT_BIT_DEPTH, "");
+ wave->setLogical(true);
+ wave->setName(name);
+ wave->setPath(gu_getCurrentPath() + G_SLASH + wave->getName());
+
+ *out = wave;
+
+ gu_log("[waveManager::createEmpty] new empty Wave created, %d frames\n", size);
+
+ return G_RES_OK;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+int resample(Wave* w, int quality, int samplerate)
+{
+ float ratio = samplerate / (float) w->getRate();
+ int newSizeFrames = ceil(w->getSize() * ratio);
+ int newSizeSamples = newSizeFrames * w->getChannels();
+
+ float* newData = new (std::nothrow) float[newSizeSamples];
+ if (newData == nullptr) {
+ gu_log("[waveManager::resample] unable to allocate memory\n");
+ return G_RES_ERR_MEMORY;
+ }
+
+ SRC_DATA src_data;
+ src_data.data_in = w->getData();
+ src_data.input_frames = w->getSize();
+ src_data.data_out = newData;
+ src_data.output_frames = newSizeFrames;
+ src_data.src_ratio = ratio;
+
+ gu_log("[waveManager::resample] resampling: new size=%d (%d frames)\n",
+ newSizeSamples, newSizeFrames);
+
+ int ret = src_simple(&src_data, quality, w->getChannels());
+ if (ret != 0) {
+ gu_log("[waveManager::resample] resampling error: %s\n", src_strerror(ret));
+ delete[] newData;
+ return G_RES_ERR_PROCESSING;
+ }
+
+ w->free();
+ w->setData(newData, newSizeSamples);
+ w->setRate(samplerate);
+
+ return G_RES_OK;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+int save(Wave* w, const string& path)
+{
+ SF_INFO header;
+ header.samplerate = w->getRate();
+ header.channels = w->getChannels();
+ header.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT;
+
+ SNDFILE* file = sf_open(path.c_str(), SFM_WRITE, &header);
+ if (file == nullptr) {
+ gu_log("[waveManager::save] unable to open %s for exporting: %s\n",
+ path.c_str(), sf_strerror(file));
+ return G_RES_ERR_IO;
+ }
+
+ if (sf_writef_float(file, w->getData(), w->getSize()) != w->getSize())
+ gu_log("[waveManager::save] warning: incomplete write!\n");
+
+ sf_close(file);
+
+ w->setLogical(false);
+ w->setEdited(false);
+
+ return G_RES_OK;
+}
+}}}; // giada::m::waveManager
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2017 Giovanni A. Zuliani | Monocasual
+ *
+ * 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 G_WAVE_MANAGER_H
+#define G_WAVE_MANAGER_H
+
+
+#include <string>
+
+
+class Wave;
+
+
+namespace giada {
+namespace m {
+namespace waveManager
+{
+/* create
+Creates a new Wave object with data read from file 'path'. */
+
+int create(const std::string& path, Wave** out);
+
+/* createEmpty
+Creates a new silent Wave object. Note: 'size' must take 2 channels into account
+(stereo). */
+
+int createEmpty(int size, int samplerate, const std::string& name, Wave** out);
+int resample(Wave* w, int quality, int samplerate);
+int save(Wave* w, const std::string& path);
+
+}}}; // giada::m::waveManager
+
+#endif
\ No newline at end of file
#include "../gui/elems/mainWindow/keyboard/channelButton.h"
#include "../utils/gui.h"
#include "../utils/fs.h"
+#include "../utils/log.h"
#include "../core/kernelAudio.h"
#include "../core/mixerHandler.h"
#include "../core/mixer.h"
#include "../core/sampleChannel.h"
#include "../core/midiChannel.h"
#include "../core/plugin.h"
+#include "../core/waveManager.h"
#include "main.h"
#include "channel.h"
-extern gdMainWindow *G_MainWin;
+extern gdMainWindow* G_MainWin;
using std::string;
static bool __soloSession__ = false;
-int glue_loadChannel(SampleChannel *ch, const string &fname)
+int glue_loadChannel(SampleChannel* ch, const string& fname)
{
/* Always stop a channel before loading a new sample in it. This will prevent
issues if tracker is outside the boundaries of the new sample -> segfault. */
conf::samplePath = gu_dirname(fname);
- int result = ch->load(fname.c_str(), conf::samplerate, conf::rsmpQuality);
+ Wave* wave = nullptr;
+ int result = waveManager::create(fname, &wave);
+ if (result != G_RES_OK)
+ return result;
+
+ if (wave->getRate() != conf::samplerate) {
+ gu_log("[glue_loadChannel] input rate (%d) != system rate (%d), conversion needed\n",
+ wave->getRate(), conf::samplerate);
+ result = waveManager::resample(wave, conf::rsmpQuality, conf::samplerate);
+ if (result != G_RES_OK) {
+ delete wave;
+ return result;
+ }
+ }
- if (result == SAMPLE_LOADED_OK)
- G_MainWin->keyboard->updateChannel(ch->guiChannel);
+ ch->pushWave(wave);
+
+ G_MainWin->keyboard->updateChannel(ch->guiChannel);
return result;
}
/* -------------------------------------------------------------------------- */
-Channel *glue_addChannel(int column, int type)
+Channel* glue_addChannel(int column, int type)
{
- Channel *ch = mh::addChannel(type);
- geChannel *gch = G_MainWin->keyboard->addChannel(column, ch);
+ Channel* ch = mh::addChannel(type);
+ geChannel* gch = G_MainWin->keyboard->addChannel(column, ch);
ch->guiChannel = gch;
return ch;
}
/* -------------------------------------------------------------------------- */
-void glue_deleteChannel(Channel *ch)
+void glue_deleteChannel(Channel* ch)
{
if (!gdConfirmWin("Warning", "Delete channel: are you sure?"))
return;
double fPpart = modf(v, &fIpart);
int iIpart = fIpart;
int iPpart = ceilf(fPpart);
- glue_setBpm(gu_itoa(iIpart).c_str(), gu_itoa(iPpart).c_str());
+ glue_setBpm(gu_toString(iIpart).c_str(), gu_toString(iPpart).c_str());
}
void glue_resetToInitState(bool resetGui, bool createColumns)
{
+ gu_closeAllSubwindows();
mixer::close();
+ clock::init(conf::samplerate, conf::midiTCfps);
mixer::init(clock::getTotalFrames(), kernelAudio::getRealBufSize());
recorder::init();
#ifdef WITH_VST
#include "../gui/dialogs/gd_mainWindow.h"
#include "../gui/dialogs/sampleEditor.h"
#include "../gui/dialogs/gd_warnings.h"
+#include "../gui/elems/basics/button.h"
#include "../gui/elems/sampleEditor/waveTools.h"
#include "../gui/elems/sampleEditor/volumeTool.h"
#include "../gui/elems/sampleEditor/boostTool.h"
return se;
}
+
/* -------------------------------------------------------------------------- */
void cut(SampleChannel* ch, int a, int b)
{
- if (!wfx_cut(ch->wave, a, b)) {
+ if (!wfx::cut(ch->wave, a, b)) {
gdAlert("Unable to cut the sample!");
return;
}
- setBeginEndChannel(ch, ch->begin, ch->end);
+ setBeginEndChannel(ch, ch->getBegin(), ch->getEnd());
gdSampleEditor* gdEditor = getSampleEditorWindow();
- gdEditor->waveTools->waveform->clearSel();
- gdEditor->waveTools->waveform->refresh();
+ gdEditor->waveTools->waveform->clearSel();
+ gdEditor->waveTools->waveform->refresh();
+ gdEditor->updateInfo();
}
void silence(SampleChannel* ch, int a, int b)
{
- wfx_silence(ch->wave, a, b);
+ wfx::silence(ch->wave, a, b);
gdSampleEditor* gdEditor = getSampleEditorWindow();
gdEditor->waveTools->waveform->refresh();
}
void fade(SampleChannel* ch, int a, int b, int type)
{
- wfx_fade(ch->wave, a, b, type);
+ wfx::fade(ch->wave, a, b, type);
gdSampleEditor* gdEditor = getSampleEditorWindow();
gdEditor->waveTools->waveform->refresh();
}
void smoothEdges(SampleChannel* ch, int a, int b)
{
- wfx_smooth(ch->wave, a, b);
+ wfx::smooth(ch->wave, a, b);
gdSampleEditor* gdEditor = getSampleEditorWindow();
gdEditor->waveTools->waveform->refresh();
}
void setStartEnd(SampleChannel* ch, int a, int b)
{
- setBeginEndChannel(ch, a * 2, b * 2); // stereo values
+ setBeginEndChannel(ch, a, b);
gdSampleEditor* gdEditor = getSampleEditorWindow();
gdEditor->waveTools->waveform->recalcPoints();
gdEditor->waveTools->waveform->clearSel();
void trim(SampleChannel* ch, int a, int b)
{
- if (!wfx_trim(ch->wave, a, b)) {
+ if (!wfx::trim(ch->wave, a, b)) {
gdAlert("Unable to trim the sample!");
return;
}
- setBeginEndChannel(ch, ch->begin, ch->end);
+ setBeginEndChannel(ch, ch->getBegin(), ch->getEnd());
+ gdSampleEditor* gdEditor = getSampleEditorWindow();
+ gdEditor->waveTools->waveform->clearSel();
+ gdEditor->waveTools->waveform->refresh();
+ gdEditor->updateInfo();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void setPlayHead(SampleChannel* ch, int f)
+{
+ ch->setTrackerPreview(f);
+ gdSampleEditor* gdEditor = getSampleEditorWindow();
+ gdEditor->waveTools->waveform->redraw();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void setPreview(SampleChannel* ch, int mode)
+{
+ ch->setPreviewMode(mode);
gdSampleEditor* gdEditor = getSampleEditorWindow();
- gdEditor->waveTools->waveform->clearSel();
- gdEditor->waveTools->waveform->refresh();
+ gdEditor->play->value(!gdEditor->play->value());
}
+
+/* -------------------------------------------------------------------------- */
+
+
+void rewindPreview(SampleChannel* ch)
+{
+ geWaveform* waveform = getSampleEditorWindow()->waveTools->waveform;
+ if (waveform->isSelected() && ch->getTrackerPreview() != waveform->getSelectionA())
+ setPlayHead(ch, waveform->getSelectionA());
+ else
+ setPlayHead(ch, 0);
+}
}}}; // giada::c::sampleEditor::
void smoothEdges(SampleChannel* ch, int a, int b);
void setStartEnd(SampleChannel* ch, int a, int b);
+/* setPlayHead
+Changes playhead's position. Used in preview. */
+
+void setPlayHead(SampleChannel* ch, int f);
+
+void setPreview(SampleChannel* ch, int mode);
+void rewindPreview(SampleChannel* ch);
}}}; // giada::c::sampleEditor::
#endif
/* update the new samplePath: everything now comes from the project
* folder (folderPath). Also remove any existing file. */
- string samplePath = fullPath + G_SLASH + ch->wave->basename(true);
+ string samplePath = fullPath + G_SLASH + ch->wave->getBasename(true);
if (gu_fileExists(samplePath))
remove(samplePath.c_str());
if (ch->save(samplePath.c_str()))
- ch->wave->pathfile = samplePath;
+ ch->wave->setPath(samplePath);
}
string gptcPath = fullPath + G_SLASH + gu_stripExt(name) + ".gptc";
int res = glue_loadChannel((SampleChannel*) browser->getChannel(), fullPath.c_str());
- if (res == SAMPLE_LOADED_OK) {
+ if (res == G_RES_OK) {
conf::samplePath = gu_dirname(fullPath);
browser->do_callback();
G_MainWin->delSubWindow(WID_SAMPLE_EDITOR); // if editor is open
gdAbout::gdAbout()
#ifdef WITH_VST
: gdWindow(340, 435, "About Giada")
-{
#else
: gdWindow(340, 350, "About Giada")
-{
#endif
-
+{
if (conf::aboutX)
resize(conf::aboutX, conf::aboutY, w(), h());
sprintf(
message,
"Version " G_VERSION_STR " (" BUILD_DATE ")\n\n"
- "Developed by Monocasual\n"
+ "Developed by Monocasual Laboratories\n"
"Based on FLTK (%d.%d.%d), RtAudio (%s),\n"
"RtMidi (%s), Libsamplerate, Jansson (%s),\n"
"Libsndfile (%s)"
deps::getRtMidiVersion().c_str(),
JANSSON_VERSION, deps::getLibsndfileVersion().c_str()
#ifdef WITH_VST
- , JUCE_MAJOR_VERSION, JUCE_MINOR_VERSION, JUCE_BUILDNUMBER);
-#else
- );
+ , JUCE_MAJOR_VERSION, JUCE_MINOR_VERSION, JUCE_BUILDNUMBER
#endif
+ );
int tw = 0;
int th = 0;
int lines = 7;
body = "Device name: " + kernelAudio::getDeviceName(dev) + "\n";
- body += "Total output(s): " + gu_itoa(kernelAudio::getMaxOutChans(dev)) + "\n";
- body += "Total intput(s): " + gu_itoa(kernelAudio::getMaxInChans(dev)) + "\n";
- body += "Duplex channel(s): " + gu_itoa(kernelAudio::getDuplexChans(dev)) + "\n";
+ body += "Total output(s): " + gu_toString(kernelAudio::getMaxOutChans(dev)) + "\n";
+ body += "Total intput(s): " + gu_toString(kernelAudio::getMaxInChans(dev)) + "\n";
+ body += "Duplex channel(s): " + gu_toString(kernelAudio::getDuplexChans(dev)) + "\n";
body += "Default output: " + string(kernelAudio::isDefaultOut(dev) ? "yes" : "no") + "\n";
body += "Default input: " + string(kernelAudio::isDefaultIn(dev) ? "yes" : "no") + "\n";
int totalFreq = kernelAudio::getTotalFreqs(dev);
- body += "Supported frequencies: " + gu_itoa(totalFreq);
+ body += "Supported frequencies: " + gu_toString(totalFreq);
for (int i=0; i<totalFreq; i++) {
if (i % 6 == 0) {
body += "\n "; // add new line each 6 printed freqs AND on the first line (i % 0 != 0)
lines++;
}
- body += gu_itoa( kernelAudio::getFreq(dev, i)) + " ";
+ body += gu_toString( kernelAudio::getFreq(dev, i)) + " ";
}
text->copy_label(body.c_str());
if (stackType == pluginHost::MASTER_IN)
label("Master In Plugins");
else {
- string l = "Channel " + gu_itoa(ch->index+1) + " Plugins";
+ string l = "Channel " + gu_toString(ch->index+1) + " Plugins";
copy_label(l.c_str());
}
conf::midiInputH, "MIDI Input Setup"),
ch(ch)
{
- string title = "MIDI Input Setup (channel " + gu_itoa(ch->index+1) + ")";
+ string title = "MIDI Input Setup (channel " + gu_toString(ch->index+1) + ")";
label(title.c_str());
size_range(G_DEFAULT_MIDI_INPUT_UI_W, G_DEFAULT_MIDI_INPUT_UI_H);
#include "../../core/sampleChannel.h"
#include "../../core/mixer.h"
#include "../../core/wave.h"
-#include "../../core/clock.h"
#include "../../utils/gui.h"
#include "../../utils/string.h"
#include "../elems/basics/button.h"
#include "sampleEditor.h"
+using std::string;
using namespace giada::m;
using namespace giada::c;
-gdSampleEditor::gdSampleEditor(SampleChannel *ch)
+gdSampleEditor::gdSampleEditor(SampleChannel* ch)
: gdWindow(640, 480),
ch(ch)
{
- begin();
+ Fl_Group* upperBar = createUpperBar();
+
+ waveTools = new geWaveTools(8, upperBar->y()+upperBar->h()+8, w()-16, h()-128,
+ ch);
+
+ Fl_Group* bottomBar = createBottomBar(8, waveTools->y()+waveTools->h()+8,
+ h()-waveTools->h()-upperBar->h()-32);
+
+ add(upperBar);
+ add(waveTools);
+ add(bottomBar);
- /* top bar: grid and zoom tools */
+ resizable(waveTools);
- Fl_Group *bar = new Fl_Group(8, 8, w()-16, 20);
- bar->begin();
- grid = new geChoice(bar->x(), bar->y(), 50, 20);
- snap = new geCheck(grid->x()+grid->w()+4, bar->y(), 12, 12);
- sep1 = new geBox(snap->x()+snap->w()+4, bar->y(), 506, 20);
- zoomOut = new geButton(sep1->x()+sep1->w()+4, bar->y(), 20, 20, "", zoomOutOff_xpm, zoomOutOn_xpm);
- zoomIn = new geButton(zoomOut->x()+zoomOut->w()+4, bar->y(), 20, 20, "", zoomInOff_xpm, zoomInOn_xpm);
- bar->end();
- bar->resizable(sep1);
+ gu_setFavicon(this);
+ set_non_modal();
+ label(ch->wave->getName().c_str());
- /* waveform */
+ size_range(720, 480);
+ if (conf::sampleEditorX)
+ resize(conf::sampleEditorX, conf::sampleEditorY, conf::sampleEditorW,
+ conf::sampleEditorH);
+
+ show();
+}
- waveTools = new geWaveTools(8, bar->y()+bar->h()+8, w()-16, h()-128, ch);
- waveTools->end();
- /* other tools */
- Fl_Group *row1 = new Fl_Group(8, waveTools->y()+waveTools->h()+8, w()-16, 20);
- row1->begin();
- volumeTool = new geVolumeTool(row1->x(), row1->y(), ch);
- boostTool = new geBoostTool(volumeTool->x()+volumeTool->w()+4, row1->y(), ch);
- panTool = new gePanTool(boostTool->x()+boostTool->w()+4, row1->y(), ch);
- row1->end();
- row1->resizable(0);
+/* -------------------------------------------------------------------------- */
- Fl_Group *row2 = new Fl_Group(8, row1->y()+row1->h()+8, 800, 20);
- row2->begin();
- pitchTool = new gePitchTool(row2->x(), row2->y(), ch);
- row2->end();
- row2->resizable(0);
- Fl_Group *row3 = new Fl_Group(8, row2->y()+row2->h()+8, w()-16, 20);
- row3->begin();
- rangeTool = new geRangeTool(row3->x(), row3->y(), ch);
- sep2 = new geBox(rangeTool->x()+rangeTool->w()+4, row3->y(), 246, 20);
- reload = new geButton(sep2->x()+sep2->w()+4, row3->y(), 70, 20, "Reload");
- row3->end();
- row3->resizable(sep2);
+gdSampleEditor::~gdSampleEditor()
+{
+ conf::sampleEditorX = x();
+ conf::sampleEditorY = y();
+ conf::sampleEditorW = w();
+ conf::sampleEditorH = h();
+ conf::sampleEditorGridVal = atoi(grid->text());
+ conf::sampleEditorGridOn = snap->value();
+ sampleEditor::setPreview(ch, G_PREVIEW_NONE);
+}
- end();
- /* grid tool setup */
+/* -------------------------------------------------------------------------- */
+
+
+Fl_Group* gdSampleEditor::createUpperBar()
+{
+ Fl_Group* g = new Fl_Group(8, 8, w()-16, 20);
+ g->begin();
+ grid = new geChoice(g->x(), g->y(), 50, 20);
+ snap = new geCheck(grid->x()+grid->w()+4, g->y()+3, 12, 12, "Snap");
+ sep1 = new geBox(snap->x()+snap->w()+4, g->y(), 506, 20);
+ zoomOut = new geButton(sep1->x()+sep1->w()+4, g->y(), 20, 20, "", zoomOutOff_xpm, zoomOutOn_xpm);
+ zoomIn = new geButton(zoomOut->x()+zoomOut->w()+4, g->y(), 20, 20, "", zoomInOff_xpm, zoomInOn_xpm);
+ g->end();
+ g->resizable(sep1);
grid->add("(off)");
grid->add("2");
if (conf::sampleEditorGridVal == 0)
grid->value(0);
else
- grid->value(grid->find_item(gu_itoa(conf::sampleEditorGridVal).c_str()));
+ grid->value(grid->find_item(gu_toString(conf::sampleEditorGridVal).c_str()));
grid->callback(cb_changeGrid, (void*)this);
snap->value(conf::sampleEditorGridOn);
/* TODO - redraw grid if != (off) */
- reload->callback(cb_reload, (void*)this);
-
zoomOut->callback(cb_zoomOut, (void*)this);
zoomIn->callback(cb_zoomIn, (void*)this);
- /* logical samples (aka takes) cannot be reloaded. So far. */
+ return g;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
- if (ch->wave->isLogical)
+Fl_Group* gdSampleEditor::createOpTools(int x, int y, int h)
+{
+ Fl_Group* g = new Fl_Group(x, y, 572, h);
+ g->begin();
+ g->resizable(0);
+ volumeTool = new geVolumeTool(g->x(), g->y(), ch);
+ boostTool = new geBoostTool(volumeTool->x()+volumeTool->w()+4, g->y(), ch);
+ panTool = new gePanTool(boostTool->x()+boostTool->w()+4, g->y(), ch);
+
+ pitchTool = new gePitchTool(g->x(), panTool->y()+panTool->h()+8, ch);
+
+ rangeTool = new geRangeTool(g->x(), pitchTool->y()+pitchTool->h()+8, ch);
+ reload = new geButton(g->x()+g->w()-70, rangeTool->y(), 70, 20, "Reload");
+ g->end();
+
+ if (ch->wave->isLogical()) // Logical samples (aka takes) cannot be reloaded.
reload->deactivate();
- gu_setFavicon(this);
- size_range(640, 480);
- resizable(waveTools);
+ reload->callback(cb_reload, (void*)this);
- label(ch->wave->name.c_str());
+ return g;
+}
- set_non_modal();
- if (conf::sampleEditorX)
- resize(conf::sampleEditorX, conf::sampleEditorY, conf::sampleEditorW, conf::sampleEditorH);
+/* -------------------------------------------------------------------------- */
- show();
+
+Fl_Group* gdSampleEditor::createPreviewBox(int x, int y, int h)
+{
+ Fl_Group* g = new Fl_Group(x, y, 110, h);
+ g->begin();
+ rewind = new geButton(g->x(), g->y()+(g->h()/2)-12, 25, 25, "", rewindOff_xpm, rewindOn_xpm);
+ play = new geButton(rewind->x()+rewind->w()+4, g->y()+(g->h()/2)-12, 25, 25, "", play_xpm, pause_xpm);
+ loop = new geCheck(play->x()+play->w()+6, g->y()+(g->h()/2)-6, 12, 12, "Loop");
+ g->end();
+
+ play->callback(cb_togglePreview, (void*)this);
+ rewind->callback(cb_rewindPreview, (void*)this);
+
+ ch->setOnEndPreviewCb([this] {
+ play->value(0);
+ });
+
+ return g;
}
/* -------------------------------------------------------------------------- */
-gdSampleEditor::~gdSampleEditor()
+Fl_Group* gdSampleEditor::createInfoBox(int x, int y, int h)
{
- conf::sampleEditorX = x();
- conf::sampleEditorY = y();
- conf::sampleEditorW = w();
- conf::sampleEditorH = h();
- conf::sampleEditorGridVal = atoi(grid->text());
- conf::sampleEditorGridOn = snap->value();
+ Fl_Group* g = new Fl_Group(x, y, 400, h);
+ g->begin();
+ info = new geBox(g->x(), g->y(), g->w(), g->h());
+ g->end();
+
+ info->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE | FL_ALIGN_TOP);
+
+ updateInfo();
+
+ return g;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Fl_Group* gdSampleEditor::createBottomBar(int x, int y, int h)
+{
+ Fl_Group* g = new Fl_Group(8, waveTools->y()+waveTools->h()+8, w()-16, h);
+ g->begin();
+ Fl_Group* previewBox = createPreviewBox(g->x(), g->y(), g->h());
+
+ geBox* divisor1 = new geBox(previewBox->x()+previewBox->w()+8, g->y(), 1, g->h());
+ divisor1->box(FL_BORDER_BOX);
+
+ Fl_Group* opTools = createOpTools(divisor1->x()+divisor1->w()+12, g->y(), g->h());
+
+ geBox* divisor2 = new geBox(opTools->x()+opTools->w()+8, g->y(), 1, g->h());
+ divisor2->box(FL_BORDER_BOX);
+
+ createInfoBox(divisor2->x()+divisor2->w()+8, g->y(), g->h());
+
+ g->end();
+ g->resizable(0);
+
+ return g;
}
/* -------------------------------------------------------------------------- */
-void gdSampleEditor::cb_reload (Fl_Widget *w, void *p) { ((gdSampleEditor*)p)->__cb_reload(); }
-void gdSampleEditor::cb_zoomIn (Fl_Widget *w, void *p) { ((gdSampleEditor*)p)->__cb_zoomIn(); }
-void gdSampleEditor::cb_zoomOut (Fl_Widget *w, void *p) { ((gdSampleEditor*)p)->__cb_zoomOut(); }
-void gdSampleEditor::cb_changeGrid(Fl_Widget *w, void *p) { ((gdSampleEditor*)p)->__cb_changeGrid(); }
-void gdSampleEditor::cb_enableSnap(Fl_Widget *w, void *p) { ((gdSampleEditor*)p)->__cb_enableSnap(); }
+void gdSampleEditor::cb_reload (Fl_Widget* w, void* p) { ((gdSampleEditor*)p)->__cb_reload(); }
+void gdSampleEditor::cb_zoomIn (Fl_Widget* w, void* p) { ((gdSampleEditor*)p)->__cb_zoomIn(); }
+void gdSampleEditor::cb_zoomOut (Fl_Widget* w, void* p) { ((gdSampleEditor*)p)->__cb_zoomOut(); }
+void gdSampleEditor::cb_changeGrid (Fl_Widget* w, void* p) { ((gdSampleEditor*)p)->__cb_changeGrid(); }
+void gdSampleEditor::cb_enableSnap (Fl_Widget* w, void* p) { ((gdSampleEditor*)p)->__cb_enableSnap(); }
+void gdSampleEditor::cb_togglePreview(Fl_Widget* w, void* p) { ((gdSampleEditor*)p)->__cb_togglePreview(); }
+void gdSampleEditor::cb_rewindPreview(Fl_Widget* w, void* p) { ((gdSampleEditor*)p)->__cb_rewindPreview(); }
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
+void gdSampleEditor::__cb_togglePreview()
+{
+ if (play->value())
+ sampleEditor::setPreview(ch, G_PREVIEW_NONE);
+ else
+ sampleEditor::setPreview(ch, loop->value() ? G_PREVIEW_LOOP : G_PREVIEW_NORMAL);
+}
+
+
+void gdSampleEditor::__cb_rewindPreview()
+{
+ sampleEditor::rewindPreview(ch);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
void gdSampleEditor::__cb_reload()
{
if (!gdConfirmWin("Warning", "Reload sample: are you sure?"))
return;
- /* no need for glue_loadChan, there's no gui to refresh */
-
- ch->load(ch->wave->pathfile.c_str(), conf::samplerate, conf::rsmpQuality);
+ if (glue_loadChannel(ch, ch->wave->getPath()) != G_RES_OK)
+ return;
glue_setBoost(ch, G_DEFAULT_BOOST);
glue_setPitch(ch, G_DEFAULT_PITCH);
- glue_setPanning(ch, 1.0f);
+ glue_setPanning(ch, 0.5f);
panTool->refresh();
boostTool->refresh();
waveTools->waveform->stretchToWindow();
waveTools->updateWaveform();
- sampleEditor::setBeginEndChannel(ch, 0, ch->wave->size);
+ sampleEditor::setBeginEndChannel(ch, 0, ch->wave->getSize());
redraw();
}
{
waveTools->waveform->setGridLevel(atoi(grid->text()));
}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gdSampleEditor::updateInfo()
+{
+ string bitDepth = ch->wave->getBits() != 0 ? gu_toString(ch->wave->getBits()) : "(unknown)";
+ string infoText =
+ "File: " + ch->wave->getPath() + "\n"
+ "Size: " + gu_toString(ch->wave->getSize()) + " frames\n"
+ "Duration: " + gu_toString(ch->wave->getDuration()) + " seconds\n"
+ "Bit depth: " + bitDepth + "\n"
+ "Frequency: " + gu_toString(ch->wave->getRate()) + " Hz\n";
+ info->copy_label(infoText.c_str());
+}
class gdSampleEditor : public gdWindow
{
+friend class geWaveform;
+
private:
- static void cb_reload (Fl_Widget *w, void *p);
- static void cb_zoomIn (Fl_Widget *w, void *p);
- static void cb_zoomOut (Fl_Widget *w, void *p);
- static void cb_changeGrid (Fl_Widget *w, void *p);
- static void cb_enableSnap (Fl_Widget *w, void *p);
- inline void __cb_reload();
- inline void __cb_zoomIn();
- inline void __cb_zoomOut();
- inline void __cb_changeGrid();
- inline void __cb_enableSnap();
+ Fl_Group* createUpperBar();
+ Fl_Group* createBottomBar(int x, int y, int h);
+
+ Fl_Group* createPreviewBox(int x, int y, int h);
+ Fl_Group* createOpTools(int x, int y, int h);
+ Fl_Group* createInfoBox(int x, int y, int h);
+
+ static void cb_reload (Fl_Widget* w, void* p);
+ static void cb_zoomIn (Fl_Widget* w, void* p);
+ static void cb_zoomOut (Fl_Widget* w, void* p);
+ static void cb_changeGrid(Fl_Widget* w, void* p);
+ static void cb_enableSnap(Fl_Widget* w, void* p);
+ static void cb_togglePreview(Fl_Widget* w, void* p);
+ static void cb_rewindPreview(Fl_Widget* w, void* p);
+ void __cb_reload();
+ void __cb_zoomIn();
+ void __cb_zoomOut();
+ void __cb_changeGrid();
+ void __cb_enableSnap();
+ void __cb_togglePreview();
+ void __cb_rewindPreview();
public:
- gdSampleEditor(SampleChannel *ch);
+ gdSampleEditor(SampleChannel* ch);
~gdSampleEditor();
- geChoice *grid;
- geCheck *snap;
- geBox *sep1;
- geButton *zoomIn;
- geButton *zoomOut;
+ void updateInfo();
+
+ geChoice* grid;
+ geCheck* snap;
+ geBox* sep1;
+ geButton* zoomIn;
+ geButton* zoomOut;
- geWaveTools *waveTools;
+ geWaveTools* waveTools;
+
+ geVolumeTool* volumeTool;
+ geBoostTool* boostTool;
+ gePanTool* panTool;
- geVolumeTool *volumeTool;
- geBoostTool *boostTool;
- gePanTool *panTool;
+ gePitchTool* pitchTool;
- gePitchTool *pitchTool;
+ geRangeTool* rangeTool;
+ geButton* reload;
- geRangeTool *rangeTool;
- geBox *sep2;
- geButton *reload;
+ geButton* play;
+ geButton* rewind;
+ geCheck* loop;
+ geBox* info;
- SampleChannel *ch;
+ SampleChannel* ch;
};
int nfreq = kernelAudio::getTotalFreqs(sounddevOut->value());
for (int i=0; i<nfreq; i++) {
int freq = kernelAudio::getFreq(sounddevOut->value(), i);
- samplerate->add(gu_itoa(freq).c_str());
+ samplerate->add(gu_toString(freq).c_str());
if (freq == conf::samplerate)
samplerate->value(i);
}
buffersize->add("1024");
buffersize->add("2048");
buffersize->add("4096");
- buffersize->showItem(gu_itoa(conf::buffersize).c_str());
+ buffersize->showItem(gu_toString(conf::buffersize).c_str());
rsmpQuality->add("Sinc best quality (very slow)");
rsmpQuality->add("Sinc medium quality (slow)");
rsmpQuality->add("Linear (very fast)");
rsmpQuality->value(conf::rsmpQuality);
- delayComp->value(gu_itoa(conf::delayComp).c_str());
+ delayComp->value(gu_toString(conf::delayComp).c_str());
delayComp->type(FL_INT_INPUT);
delayComp->maximum_size(5);
void geTabPlugins::updateCount()
{
- string scanLabel = "Scan (" + gu_itoa(pluginHost::countAvailablePlugins()) + " found)";
+ string scanLabel = "Scan (" + gu_toString(pluginHost::countAvailablePlugins()) + " found)";
scanButton->label(scanLabel.c_str());
}
void geTabPlugins::cb_onScan(float progress, void *p)
{
- string l = "Scan in progress (" + gu_itoa((int)(progress*100)) + "%). Please wait...";
+ string l = "Scan in progress (" + gu_toString((int)(progress*100)) + "%). Please wait...";
((geTabPlugins *)p)->info->label(l.c_str());
Fl::wait();
}
if (pos == -1)
pos = 0;
else
- pos = (pos * (w()-1)) / (ch->end - ch->begin);
+ pos = (pos * (w()-1)) / ((ch->getEnd() - ch->getBegin()));
fl_rectf(x()+1, y()+1, pos, h()-2, G_COLOR_LIGHT_1);
}
}
gu_log("[geColumn::handle] loading %s...\n", paths.at(i).c_str());
SampleChannel *c = (SampleChannel*) glue_addChannel(index, CHANNEL_SAMPLE);
result = glue_loadChannel(c, gu_stripFileUrl(paths.at(i)).c_str());
- if (result != SAMPLE_LOADED_OK) {
+ if (result != G_RES_OK) {
deleteChannel(c->guiChannel);
fails = true;
}
/* -------------------------------------------------------------------------- */
-void geKeyboard::freeChannel(geChannel *gch)
+void geKeyboard::freeChannel(geChannel* gch)
{
gch->reset();
}
/* -------------------------------------------------------------------------- */
-void geKeyboard::deleteChannel(geChannel *gch)
+void geKeyboard::deleteChannel(geChannel* gch)
{
for (unsigned i=0; i<columns.size(); i++) {
int k = columns.at(i)->find(gch);
/* -------------------------------------------------------------------------- */
-void geKeyboard::updateChannel(geChannel *gch)
+void geKeyboard::updateChannel(geChannel* gch)
{
gch->update();
}
void geKeyboard::organizeColumns()
{
- /* if only one column exists don't cleanup: the initial column must
- * stay here. */
-
- if (columns.size() == 1)
+ if (columns.size() == 0)
return;
- /* otherwise delete all empty columns */
- /** FIXME - this for loop might not work correctly! */
+ /* Otherwise delete all empty columns. */
- for (unsigned i=columns.size()-1; i>=1; i--) {
+ for (size_t i=columns.size(); i-- > 0;) {
if (columns.at(i)->isEmpty()) {
- //Fl::delete_widget(columns.at(i));
- delete columns.at(i);
+ Fl::delete_widget(columns.at(i));
columns.erase(columns.begin() + i);
}
}
- /* compact column, avoid empty spaces */
-
- for (unsigned i=1; i<columns.size(); i++)
- columns.at(i)->position(columns.at(i-1)->x() + columns.at(i-1)->w() + 16, y());
+ /* Zero columns? Just add the "add column" button. Compact column and avoid
+ empty spaces otherwise. */
- addColumnBtn->position(columns.back()->x() + columns.back()->w() + 16, y());
-
- /* recompute col indexes */
+ if (columns.size() == 0)
+ addColumnBtn->position(x() - xposition(), y());
+ else {
+ for (size_t i=0; i<columns.size(); i++) {
+ int pos = i == 0 ? x() - xposition() : columns.at(i-1)->x() + columns.at(i-1)->w() + COLUMN_GAP;
+ columns.at(i)->position(pos, y());
+ }
+ addColumnBtn->position(columns.back()->x() + columns.back()->w() + COLUMN_GAP, y());
+ }
refreshColIndexes();
-
redraw();
}
/* -------------------------------------------------------------------------- */
-void geKeyboard::cb_addColumn(Fl_Widget *v, void *p)
+void geKeyboard::cb_addColumn(Fl_Widget* v, void* p)
{
((geKeyboard*)p)->__cb_addColumn(G_DEFAULT_COLUMN_WIDTH);
}
/* -------------------------------------------------------------------------- */
-geChannel *geKeyboard::addChannel(int colIndex, Channel *ch, bool build)
+geChannel* geKeyboard::addChannel(int colIndex, Channel* ch, bool build)
{
- geColumn *col = getColumnByIndex(colIndex);
+ geColumn* col = getColumnByIndex(colIndex);
/* no column with index 'colIndex' found? Just create it and set its index
to 'colIndex'. */
/* -------------------------------------------------------------------------- */
-geColumn *geKeyboard::getColumnByIndex(int index)
+geColumn* geKeyboard::getColumnByIndex(int index)
{
for (unsigned i=0; i<columns.size(); i++)
if (columns.at(i)->getIndex() == index)
for (unsigned i=0; i<columns.size(); i++)
for (int k=1; k<columns.at(i)->children(); k++)
- ret &= ((geChannel*)columns.at(i)->child(k))->keyPress(e);
+ ret &= static_cast<geChannel*>(columns.at(i)->child(k))->keyPress(e);
break;
}
}
void geKeyboard::clear()
{
for (unsigned i=0; i<columns.size(); i++)
- delete columns.at(i);
+ Fl::delete_widget(columns.at(i));
columns.clear();
indexColumn = 0; // new columns will start from index=0
addColumnBtn->position(8, y());
/* -------------------------------------------------------------------------- */
-void geKeyboard::setChannelWithActions(geSampleChannel *gch)
+void geKeyboard::setChannelWithActions(geSampleChannel* gch)
{
if (gch->ch->hasActions)
gch->showActionButton();
void geKeyboard::printChannelMessage(int res)
{
- if (res == SAMPLE_NOT_VALID)
- gdAlert("This is not a valid WAVE file.");
- else if (res == SAMPLE_MULTICHANNEL)
+ if (res == G_RES_ERR_WRONG_DATA)
gdAlert("Multichannel samples not supported.");
- else if (res == SAMPLE_WRONG_BIT)
- gdAlert("This sample has an\nunsupported bit-depth (> 32 bit).");
- else if (res == SAMPLE_WRONG_ENDIAN)
- gdAlert("This sample has a wrong\nbyte order (not little-endian).");
- else if (res == SAMPLE_WRONG_FORMAT)
- gdAlert("This sample is encoded in\nan unsupported audio format.");
- else if (res == SAMPLE_READ_ERROR)
+ else if (res == G_RES_ERR_IO)
gdAlert("Unable to read this sample.");
- else if (res == SAMPLE_PATH_TOO_LONG)
+ else if (res == G_RES_ERR_PATH_TOO_LONG)
gdAlert("File path too long.");
else
gdAlert("Unknown error.");
{
int colx;
int colxw;
- int gap = 16;
if (columns.size() == 0) {
colx = x() - xposition(); // mind the offset with xposition()
colxw = colx + width;
}
else {
- geColumn *prev = columns.back();
- colx = prev->x()+prev->w() + gap;
+ geColumn* prev = columns.back();
+ colx = prev->x()+prev->w() + COLUMN_GAP;
colxw = colx + width;
}
/* add geColumn to geKeyboard and to columns vector */
- geColumn *gc = new geColumn(colx, y(), width, 2000, indexColumn, this);
+ geColumn* gc = new geColumn(colx, y(), width, 2000, indexColumn, this);
add(gc);
columns.push_back(gc);
indexColumn++;
/* move addColumn button */
- addColumnBtn->position(colxw + gap, y());
+ addColumnBtn->position(colxw + COLUMN_GAP, y());
redraw();
gu_log("[geKeyboard::__cb_addColumn] new column added (index=%d, w=%d), total count=%d, addColumn(x)=%d\n",
/* -------------------------------------------------------------------------- */
-geColumn *geKeyboard::getColumn(int i)
+geColumn* geKeyboard::getColumn(int i)
{
return columns.at(i);
}
{
private:
+ static const int COLUMN_GAP = 16;
+
/* refreshColIndexes
* Recompute all column indexes in order to avoid any gaps between them.
* Indexes must always be contiguous! */
void refreshColIndexes();
- static void cb_addColumn (Fl_Widget *v, void *p);
+ static void cb_addColumn (Fl_Widget* v, void* p);
inline void __cb_addColumn(int width=G_DEFAULT_COLUMN_WIDTH);
bool bckspcPressed;
static int indexColumn;
- geButton *addColumnBtn;
+ geButton* addColumnBtn;
/* columns
* a vector of columns which in turn contain channels. */
* set to true, also generate the corresponding column if column (index) does
* not exist yet. */
- geChannel *addChannel(int column, Channel *ch, bool build=false);
+ geChannel* addChannel(int column, Channel* ch, bool build=false);
/* addColumn
* add a new column to the top of the stack. */
* delete a channel from geChannels<> where geChannel->ch == ch and remove
* it from the stack. */
- void deleteChannel(geChannel *gch);
+ void deleteChannel(geChannel* gch);
/* freeChannel
* free a channel from geChannels<> where geChannel->ch == ch. No channels
* are deleted */
- void freeChannel(geChannel *gch);
+ void freeChannel(geChannel* gch);
/* updateChannel
* wrapper function to call gch->update(). */
- void updateChannel(geChannel *gch);
+ void updateChannel(geChannel* gch);
/* organizeColumns
* reorganize columns layout by removing empty gaps. */
/* getColumnByIndex
* return the column with index 'index', or nullptr if not found. */
- geColumn *getColumnByIndex(int index);
+ geColumn* getColumnByIndex(int index);
/* getColumn
* return the column with from columns->at(i). */
- geColumn *getColumn(int i);
+ geColumn* getColumn(int i);
/* clear
* delete all channels and groups. */
/* setChannelWithActions
* add 'R' button if channel has actions, and set recorder to active. */
- void setChannelWithActions(geSampleChannel *gch);
+ void setChannelWithActions(geSampleChannel* gch);
/* printChannelMessage
* given any output by glue_loadChannel, print the message on screen
mainButton->label("* file not found! *");
break;
default:
- mainButton->label(((SampleChannel*) ch)->wave->name.c_str());
+ mainButton->label(((SampleChannel*) ch)->wave->getName().c_str());
break;
}
geSampleChannel *gch = static_cast<geSampleChannel*>(parent());
SampleChannel *ch = static_cast<SampleChannel*>(gch->ch);
int result = glue_loadChannel(ch, gu_trim(gu_stripFileUrl(Fl::event_text())).c_str());
- if (result != SAMPLE_LOADED_OK)
+ if (result != G_RES_OK)
G_MainWin->keyboard->printChannelMessage(result);
ret = 1;
break;
#include "mainMenu.h"
-extern gdMainWindow *G_MainWin;
+extern gdMainWindow* G_MainWin;
using namespace giada::m;
/* -------------------------------------------------------------------------- */
-void geMainMenu::cb_about (Fl_Widget *v, void *p) { ((geMainMenu*)p)->__cb_about(); }
-void geMainMenu::cb_config(Fl_Widget *v, void *p) { ((geMainMenu*)p)->__cb_config(); }
-void geMainMenu::cb_file (Fl_Widget *v, void *p) { ((geMainMenu*)p)->__cb_file(); }
-void geMainMenu::cb_edit (Fl_Widget *v, void *p) { ((geMainMenu*)p)->__cb_edit(); }
+void geMainMenu::cb_about (Fl_Widget* v, void* p) { ((geMainMenu*)p)->__cb_about(); }
+void geMainMenu::cb_config(Fl_Widget* v, void* p) { ((geMainMenu*)p)->__cb_config(); }
+void geMainMenu::cb_file (Fl_Widget* v, void* p) { ((geMainMenu*)p)->__cb_file(); }
+void geMainMenu::cb_edit (Fl_Widget* v, void* p) { ((geMainMenu*)p)->__cb_edit(); }
/* -------------------------------------------------------------------------- */
{0}
};
- Fl_Menu_Button *b = new Fl_Menu_Button(0, 0, 100, 50);
+ Fl_Menu_Button* b = new Fl_Menu_Button(0, 0, 100, 50);
b->box(G_CUSTOM_BORDER_BOX);
b->textsize(G_GUI_FONT_SIZE_BASE);
b->textcolor(G_COLOR_LIGHT_2);
b->color(G_COLOR_GREY_2);
- const Fl_Menu_Item *m = menu->popup(Fl::event_x(), Fl::event_y(), 0, 0, b);
+ const Fl_Menu_Item* m = menu->popup(Fl::event_x(), Fl::event_y(), 0, 0, b);
if (!m) return;
if (strcmp(m->label(), "Open patch or project...") == 0) {
break;
}
- Fl_Menu_Button *b = new Fl_Menu_Button(0, 0, 100, 50);
+ Fl_Menu_Button* b = new Fl_Menu_Button(0, 0, 100, 50);
b->box(G_CUSTOM_BORDER_BOX);
b->textsize(G_GUI_FONT_SIZE_BASE);
b->textcolor(G_COLOR_LIGHT_2);
b->color(G_COLOR_GREY_2);
- const Fl_Menu_Item *m = menu->popup(Fl::event_x(), Fl::event_y(), 0, 0, b);
+ const Fl_Menu_Item* m = menu->popup(Fl::event_x(), Fl::event_y(), 0, 0, b);
if (!m) return;
if (strcmp(m->label(), "Clear all samples") == 0) {
if (strcmp(m->label(), "Reset to init state") == 0) {
if (!gdConfirmWin("Warning", "Reset to init state: are you sure?"))
return;
- gu_closeAllSubwindows();
glue_resetToInitState();
return;
}
#include "boostTool.h"
+using namespace giada::m;
+
+
geBoostTool::geBoostTool(int X, int Y, SampleChannel *ch)
: Fl_Group(X, Y, 220, 20),
ch (ch)
else
if (Fl::event() == FL_RELEASE) {
glue_setBoost(ch, dial->value());
- static_cast<gdSampleEditor*>(parent()->parent())->waveTools->updateWaveform();
+ static_cast<gdSampleEditor*>(window())->waveTools->updateWaveform();
}
}
void geBoostTool::__cb_setBoostNum()
{
glue_setBoost(ch, gu_dBtoLinear(atof(input->value())));
- static_cast<gdSampleEditor*>(parent()->parent())->waveTools->updateWaveform();
+ static_cast<gdSampleEditor*>(window())->waveTools->updateWaveform();
}
void geBoostTool::__cb_normalize()
{
- float val = wfx_normalizeSoft(ch->wave);
+ float val = wfx::normalizeSoft(ch->wave);
glue_setBoost(ch, val); // it's like a fake user moving the dial
- static_cast<gdSampleEditor*>(parent()->parent())->waveTools->updateWaveform();
+ static_cast<gdSampleEditor*>(window())->waveTools->updateWaveform();
}
void gePitchTool::__cb_setPitchToBar()
{
- glue_setPitch(ch, ch->end / (float) clock::getFramesPerBar());
+ glue_setPitch(ch, ch->getEnd() / (float) clock::getFramesPerBar());
}
void gePitchTool::__cb_setPitchToSong()
{
- glue_setPitch(ch, ch->end / (float) clock::getTotalFrames());
+ glue_setPitch(ch, ch->getEnd() / (float) clock::getTotalFrames());
}
using namespace giada::c;
-geRangeTool::geRangeTool(int x, int y, SampleChannel *ch)
- : Fl_Group(x, y, 300, 20),
- ch (ch)
+geRangeTool::geRangeTool(int x, int y, SampleChannel* ch)
+ : Fl_Group(x, y, 300, 20),
+ m_ch (ch)
{
begin();
- label = new geBox (x, y, gu_getStringWidth("Range"), 20, "Range", FL_ALIGN_RIGHT);
- begin_ = new geInput(label->x()+label->w()+4, y, 70, 20);
- end_ = new geInput(begin_->x()+begin_->w()+4, y, 70, 20);
- reset = new geButton(end_->x()+end_->w()+4, y, 70, 20, "Reset");
+ m_label = new geBox (x, y, gu_getStringWidth("Range"), 20, "Range", FL_ALIGN_RIGHT);
+ m_begin = new geInput(m_label->x()+m_label->w()+4, y, 70, 20);
+ m_end = new geInput(m_begin->x()+m_begin->w()+4, y, 70, 20);
+ m_reset = new geButton(m_end->x()+m_end->w()+4, y, 70, 20, "Reset");
end();
- begin_->type(FL_INT_INPUT);
- begin_->when(FL_WHEN_RELEASE | FL_WHEN_ENTER_KEY); // on focus lost or enter key
- begin_->callback(cb_setChanPos, this);
+ m_begin->type(FL_INT_INPUT);
+ m_begin->when(FL_WHEN_RELEASE | FL_WHEN_ENTER_KEY); // on focus lost or enter key
+ m_begin->callback(cb_setChanPos, this);
- end_->type(FL_INT_INPUT);
- end_->when(FL_WHEN_RELEASE | FL_WHEN_ENTER_KEY); // on focus lost or enter key
- end_->callback(cb_setChanPos, this);
+ m_end->type(FL_INT_INPUT);
+ m_end->when(FL_WHEN_RELEASE | FL_WHEN_ENTER_KEY); // on focus lost or enter key
+ m_end->callback(cb_setChanPos, this);
- reset->callback(cb_resetStartEnd, this);
+ m_reset->callback(cb_resetStartEnd, this);
refresh();
}
void geRangeTool::refresh()
{
- begin_->value(gu_itoa(ch->begin / 2).c_str());
- end_->value(gu_itoa(ch->end / 2).c_str());
+ m_begin->value(gu_toString(m_ch->getBegin()).c_str());
+ m_end->value(gu_toString(m_ch->getEnd()).c_str());
}
/* -------------------------------------------------------------------------- */
-void geRangeTool::cb_setChanPos (Fl_Widget *w, void *p) { ((geRangeTool*)p)->__cb_setChanPos(); }
-void geRangeTool::cb_resetStartEnd(Fl_Widget *w, void *p) { ((geRangeTool*)p)->__cb_resetStartEnd(); }
+void geRangeTool::cb_setChanPos (Fl_Widget* w, void* p) { ((geRangeTool*)p)->__cb_setChanPos(); }
+void geRangeTool::cb_resetStartEnd(Fl_Widget* w, void* p) { ((geRangeTool*)p)->__cb_resetStartEnd(); }
/* -------------------------------------------------------------------------- */
void geRangeTool::__cb_setChanPos()
{
- sampleEditor::setBeginEndChannel(ch, atoi(begin_->value())*2, atoi(end_->value())*2);
- static_cast<gdSampleEditor*>(parent()->parent())->waveTools->updateWaveform();
+ sampleEditor::setBeginEndChannel(m_ch, atoi(m_begin->value()), atoi(m_end->value()));
+ static_cast<gdSampleEditor*>(window())->waveTools->updateWaveform();
}
void geRangeTool::__cb_resetStartEnd()
{
- sampleEditor::setBeginEndChannel(ch, 0, ch->wave->size);
- static_cast<gdSampleEditor*>(parent()->parent())->waveTools->updateWaveform();
+ sampleEditor::setBeginEndChannel(m_ch, 0, m_ch->wave->getSize() - 1);
+ static_cast<gdSampleEditor*>(window())->waveTools->updateWaveform();
}
{
private:
- SampleChannel *ch;
+ SampleChannel* m_ch;
- geBox *label;
- geInput *begin_;
- geInput *end_;
- geButton *reset;
+ geBox* m_label;
+ geInput* m_begin;
+ geInput* m_end;
+ geButton* m_reset;
- static void cb_setChanPos (Fl_Widget *w, void *p);
- static void cb_resetStartEnd(Fl_Widget *w, void *p);
+ static void cb_setChanPos (Fl_Widget* w, void* p);
+ static void cb_resetStartEnd(Fl_Widget* w, void* p);
inline void __cb_setChanPos();
inline void __cb_resetStartEnd();
public:
- geRangeTool(int x, int y, SampleChannel *ch);
+ geRangeTool(int x, int y, SampleChannel* ch);
void refresh();
};
c::sampleEditor::silence(wavetools->ch, a, b);
break;
case Menu::FADE_IN:
- c::sampleEditor::fade(wavetools->ch, a, b, 0);
+ c::sampleEditor::fade(wavetools->ch, a, b, m::wfx::FADE_IN);
break;
case Menu::FADE_OUT:
- c::sampleEditor::fade(wavetools->ch, a, b, 1);
+ c::sampleEditor::fade(wavetools->ch, a, b, m::wfx::FADE_OUT);
break;
case Menu::SMOOTH_EDGES:
c::sampleEditor::smoothEdges(wavetools->ch, a, b);
void geWaveTools::redrawWaveformAsync()
{
- if (ch->status & (STATUS_PLAY | STATUS_ENDING))
+ if (ch->isPreview())
waveform->redraw();
}
int geWaveTools::handle(int e)
{
- int ret = Fl_Group::handle(e);
switch (e) {
case FL_MOUSEWHEEL: {
waveform->setZoom(Fl::event_dy());
redraw();
- ret = 1;
- break;
+ return 1;
}
case FL_PUSH: {
if (Fl::event_button3()) { // right button
openMenu();
- ret = 1;
+ return 1;
}
- break;
+ Fl::focus(waveform);
}
+ default:
+ return Fl_Group::handle(e);
}
- return ret;
}
#include "../../../core/sampleChannel.h"
#include "../../../glue/channel.h"
#include "../../../glue/sampleEditor.h"
+#include "../../../utils/log.h"
+#include "../../dialogs/sampleEditor.h"
#include "../basics/boxtypes.h"
#include "waveTools.h"
#include "waveform.h"
geWaveform::geWaveform(int x, int y, int w, int h, SampleChannel* ch, const char* l)
-: Fl_Widget (x, y, w, h, l),
- selection {},
- chan (ch),
- chanStart (0),
- chanStartLit(false),
- chanEnd (0),
- chanEndLit (false),
- pushed (false),
- dragged (false),
- resizedA (false),
- resizedB (false),
- ratio (0.0f)
+: Fl_Widget (x, y, w, h, l),
+ m_selection {},
+ m_ch (ch),
+ m_chanStart (0),
+ m_chanStartLit(false),
+ m_chanEnd (0),
+ m_chanEndLit (false),
+ m_pushed (false),
+ m_dragged (false),
+ m_resizedA (false),
+ m_resizedB (false),
+ m_ratio (0.0f)
{
- data.sup = nullptr;
- data.inf = nullptr;
- data.size = 0;
+ m_data.sup = nullptr;
+ m_data.inf = nullptr;
+ m_data.size = 0;
- grid.snap = conf::sampleEditorGridOn;
- grid.level = conf::sampleEditorGridVal;
+ m_grid.snap = conf::sampleEditorGridOn;
+ m_grid.level = conf::sampleEditorGridVal;
alloc(w);
}
void geWaveform::freeData()
{
- if (data.sup) {
- delete[] data.sup;
- delete[] data.inf;
- data.sup = nullptr;
- data.inf = nullptr;
- data.size = 0;
+ if (m_data.sup) {
+ delete[] m_data.sup;
+ delete[] m_data.inf;
+ m_data.sup = nullptr;
+ m_data.inf = nullptr;
+ m_data.size = 0;
}
- grid.points.clear();
+ m_grid.points.clear();
}
/* -------------------------------------------------------------------------- */
-int geWaveform::alloc(int datasize)
+int geWaveform::alloc(int datasize, bool force)
{
- ratio = chan->wave->size / (float) datasize;
+ Wave* wave = m_ch->wave;
- if (ratio < 2)
- return 0;
+ m_ratio = wave->getSize() / (float) datasize;
+
+ /* Limit 1:1 drawing (to avoid sub-frame drawing) by keeping m_ratio >= 1. */
+
+ if (m_ratio < 1) {
+ datasize = wave->getSize();
+ m_ratio = 1;
+ }
+
+ if (datasize == m_data.size && !force)
+ return 0;
freeData();
- data.size = datasize;
- data.sup = new (std::nothrow) int[data.size];
- data.inf = new (std::nothrow) int[data.size];
+ m_data.size = datasize;
+ m_data.sup = new (std::nothrow) int[m_data.size];
+ m_data.inf = new (std::nothrow) int[m_data.size];
- if (!data.sup || !data.inf)
+ if (!m_data.sup || !m_data.inf) {
+ gu_log("[geWaveform::alloc] unable to allocate memory for the waveform!\n");
return 0;
+ }
+
+ gu_log("[geWaveform::alloc] %d pixels, %f m_ratio\n", m_data.size, m_ratio);
int offset = h() / 2;
int zero = y() + offset; // center, zero amplitude (-inf dB)
- /* grid frequency: store a grid point every 'gridFreq' pixel. Must be
- * even, as always */
-
- int gridFreq = 0;
- if (grid.level != 0) {
- gridFreq = chan->wave->size / grid.level;
- if (gridFreq % 2 != 0)
- gridFreq--;
- }
+ /* Frid frequency: store a grid point every 'gridFreq' frame (if grid is
+ enabled). TODO - this will cause round off errors, since gridFreq is integer. */
- for (int i=0; i<data.size; i++) {
+ int gridFreq = m_grid.level != 0 ? wave->getSize() / m_grid.level : 0;
- int pp; // point prev
- int pn; // point next
+ /* Resampling the waveform, hardcore way. Many thanks to
+ http://fourier.eng.hmc.edu/e161/lectures/resize/node3.html */
- /* resampling the waveform, hardcore way. Many thanks to
- * http://fourier.eng.hmc.edu/e161/lectures/resize/node3.html
- * Note: we use
- * p = j * (m-1 / n)
- * instead of
- * p = j * (m-1 / n-1)
- * in order to obtain 'datasize' cells to parse (and not datasize-1) */
+ for (int i=0; i<m_data.size; i++) {
+
+ /* Scan the original waveform in chunks [pc, pn]. */
- pp = i * ((chan->wave->size - 1) / (float) datasize);
- pn = (i+1) * ((chan->wave->size - 1) / (float) datasize);
-
- if (pp % 2 != 0) pp -= 1;
- if (pn % 2 != 0) pn -= 1;
+ float pc = i * m_ratio; // current point
+ float pn = (i+1) * m_ratio; // next point
float peaksup = 0.0f;
float peakinf = 0.0f;
- /* scan the original data in chunks */
+ for (float k=pc; k<pn; k++) {
- int k = pp;
- while (k < pn) {
+ if (k >= wave->getSize())
+ continue;
- if (chan->wave->data[k] > peaksup)
- peaksup = chan->wave->data[k]; // FIXME - Left data only
- else
- if (chan->wave->data[k] <= peakinf)
- peakinf = chan->wave->data[k]; // FIXME - Left data only
+ /* Compute average of stereo signal. */
- /* print grid */
+ float avg = 0.0f;
+ float* frame = wave->getFrame(k);
+ for (int j=0; j<wave->getChannels(); j++)
+ avg += frame[j];
+ avg /= wave->getChannels();
+
+ /* Find peaks (greater and lower). */
- if (gridFreq != 0)
- if (k % gridFreq == 0 && k != 0)
- grid.points.push_back(i);
+ if (avg > peaksup) peaksup = avg;
+ else if (avg <= peakinf) peakinf = avg;
- k += 2;
+ /* Fill up grid vector. */
+
+ if (gridFreq != 0 && (int) k % gridFreq == 0 && k != 0)
+ m_grid.points.push_back(k);
}
- data.sup[i] = zero - (peaksup * chan->getBoost() * offset);
- data.inf[i] = zero - (peakinf * chan->getBoost() * offset);
+ m_data.sup[i] = zero - (peaksup * m_ch->getBoost() * offset);
+ m_data.inf[i] = zero - (peakinf * m_ch->getBoost() * offset);
// avoid window overflow
- if (data.sup[i] < y()) data.sup[i] = y();
- if (data.inf[i] > y()+h()-1) data.inf[i] = y()+h()-1;
+ if (m_data.sup[i] < y()) m_data.sup[i] = y();
+ if (m_data.inf[i] > y()+h()-1) m_data.inf[i] = y()+h()-1;
}
recalcPoints();
void geWaveform::recalcPoints()
{
- selection.aPixel = relativePoint(selection.aFrame);
- selection.bPixel = relativePoint(selection.bFrame);
- chanStart = relativePoint(chan->begin / 2);
- chanEnd = relativePoint(chan->end / 2);
+ m_chanStart = m_ch->getBegin();
+ m_chanEnd = m_ch->getEnd();
}
if (!isSelected())
return;
- int a_x = selection.aPixel + x(); // - start;
- int b_x = selection.bPixel + x(); // - start;
+ int a = frameToPixel(m_selection.a) + x();
+ int b = frameToPixel(m_selection.b) + x();
- if (a_x < 0)
- a_x = 0;
- if (b_x >= w() + BORDER)
- b_x = w() + BORDER;
+ if (a < 0)
+ a = 0;
+ if (b >= w() + BORDER)
+ b = w() + BORDER;
- if (selection.aPixel < selection.bPixel)
- fl_rectf(a_x, y(), b_x-a_x, h(), G_COLOR_GREY_4);
+ if (a < b)
+ fl_rectf(a, y(), b-a, h(), G_COLOR_GREY_4);
else
- fl_rectf(b_x, y(), a_x-b_x, h(), G_COLOR_GREY_4);
+ fl_rectf(b, y(), a-b, h(), G_COLOR_GREY_4);
}
fl_color(G_COLOR_BLACK);
for (int i=from; i<to; i++) {
- fl_line(i+x(), zero, i+x(), data.sup[i]);
- fl_line(i+x(), zero, i+x(), data.inf[i]);
+ if (i >= m_data.size)
+ break;
+ fl_line(i+x(), zero, i+x(), m_data.sup[i]);
+ fl_line(i+x(), zero, i+x(), m_data.inf[i]);
}
}
void geWaveform::drawGrid(int from, int to)
{
- fl_color(G_COLOR_GREY_3);
- fl_line_style(FL_DASH, 1, nullptr);
- for (int i=from; i<to; i++) {
- for (unsigned k=0; k<grid.points.size(); k++) {
- if (grid.points.at(k) != i)
- continue;
- fl_line(i+x(), y(), i+x(), y()+h());
- break;
- }
- }
- fl_line_style(FL_SOLID, 0, nullptr);
+ fl_color(G_COLOR_GREY_3);
+ fl_line_style(FL_DASH, 1, nullptr);
+
+ for (int pf : m_grid.points) {
+ int pp = frameToPixel(pf);
+ if (pp > from && pp < to)
+ fl_line(pp+x(), y(), pp+x(), y()+h());
+ }
+
+ fl_line_style(FL_SOLID, 0, nullptr);
}
void geWaveform::drawStartEndPoints()
{
- /* print chanStart */
+ /* print m_chanStart */
- int lineX = chanStart + x();
+ int lineX = frameToPixel(m_chanStart) + x();
- if (chanStartLit) fl_color(G_COLOR_LIGHT_2);
+ if (m_chanStartLit) fl_color(G_COLOR_LIGHT_2);
else fl_color(G_COLOR_LIGHT_1);
/* vertical line */
else
fl_rectf(lineX, y()+h()-FLAG_HEIGHT-1, FLAG_WIDTH, FLAG_HEIGHT);
- /* print chanEnd */
+ /* print m_chanEnd */
- lineX = chanEnd + x() - 1;
- if (chanEndLit) fl_color(G_COLOR_LIGHT_2);
+ lineX = frameToPixel(m_chanEnd) + x() - 1;
+ if (m_chanEndLit) fl_color(G_COLOR_LIGHT_2);
else fl_color(G_COLOR_LIGHT_1);
/* vertical line */
void geWaveform::drawPlayHead()
{
- if (chan->status == STATUS_OFF)
- return;
- int p = ceilf(chan->tracker / ratio) + x();
+ int p = frameToPixel(m_ch->getTrackerPreview()) + x();
fl_color(G_COLOR_LIGHT_2);
fl_line(p, y() + 1, p, y() + h() - 2);
}
void geWaveform::draw()
{
- assert(data.sup != nullptr);
- assert(data.inf != nullptr);
+ assert(m_data.sup != nullptr);
+ assert(m_data.inf != nullptr);
fl_rectf(x(), y(), w(), h(), G_COLOR_GREY_2); // blank canvas
int geWaveform::handle(int e)
{
- int ret = 0;
+ m_mouseX = pixelToFrame(Fl::event_x() - x());
+ m_mouseY = pixelToFrame(Fl::event_y() - y());
switch (e) {
+ case FL_KEYDOWN: {
+ if (Fl::event_key() == ' ')
+ static_cast<gdSampleEditor*>(window())->__cb_togglePreview();
+ else
+ if (Fl::event_key() == FL_BackSpace)
+ sampleEditor::rewindPreview(m_ch);
+ return 1;
+ }
+
case FL_PUSH: {
- mouseX = Fl::event_x();
- pushed = true;
+ m_pushed = true;
if (!mouseOnEnd() && !mouseOnStart()) {
- if (Fl::event_button3()) { // let the parent (waveTools) handle this
- ret = 0;
- break;
- }
+ if (Fl::event_button3()) // let the parent (waveTools) handle this
+ return 0;
if (mouseOnSelectionA())
- resizedA = true;
+ m_resizedA = true;
else
if(mouseOnSelectionB())
- resizedB = true;
+ m_resizedB = true;
else {
- dragged = true;
- selection.aPixel = Fl::event_x() - x();
- selection.bPixel = selection.aPixel;
+ m_dragged = true;
+ m_selection.a = m_mouseX;
+ m_selection.b = m_mouseX;
}
}
- ret = 1;
- break;
+ return 1;
}
case FL_RELEASE: {
- /* If selection has been done (dragged or resized), make sure that point A
+ sampleEditor::setPlayHead(m_ch, m_mouseX);
+
+ /* If selection has been done (m_dragged or resized), make sure that point A
is always lower than B. */
- if (dragged || resizedA || resizedB)
+ if (m_dragged || m_resizedA || m_resizedB)
fixSelection();
/* Handle begin/end markers interaction. */
- if (chanStartLit || chanEndLit) {
- int realChanStart = chan->begin;
- int realChanEnd = chan->end;
- if (chanStartLit)
- realChanStart = absolutePoint(chanStart) * 2;
- else
- if (chanEndLit)
- realChanEnd = absolutePoint(chanEnd) * 2;
- sampleEditor::setBeginEndChannel(chan, realChanStart, realChanEnd);
- }
+ if (m_chanStartLit || m_chanEndLit)
+ sampleEditor::setBeginEndChannel(m_ch, m_chanStart, m_chanEnd);
- pushed = false;
- dragged = false;
- resizedA = false;
- resizedB = false;
+ m_pushed = false;
+ m_dragged = false;
+ m_resizedA = false;
+ m_resizedB = false;
redraw();
- ret = 1;
- break;
+ return 1;
}
case FL_ENTER: { // enables FL_DRAG
- ret = 1;
- break;
+ return 1;
}
case FL_LEAVE: {
- if (chanStartLit || chanEndLit) {
- chanStartLit = false;
- chanEndLit = false;
+ if (m_chanStartLit || m_chanEndLit) {
+ m_chanStartLit = false;
+ m_chanEndLit = false;
redraw();
}
- ret = 1;
- break;
+ return 1;
}
case FL_MOVE: {
- mouseX = Fl::event_x();
- mouseY = Fl::event_y();
if (mouseOnStart()) {
- chanStartLit = true;
+ m_chanStartLit = true;
redraw();
}
else
- if (chanStartLit) {
- chanStartLit = false;
+ if (m_chanStartLit) {
+ m_chanStartLit = false;
redraw();
}
if (mouseOnEnd()) {
- chanEndLit = true;
+ m_chanEndLit = true;
redraw();
}
else
- if (chanEndLit) {
- chanEndLit = false;
+ if (m_chanEndLit) {
+ m_chanEndLit = false;
redraw();
}
else
fl_cursor(FL_CURSOR_DEFAULT, FL_WHITE, FL_BLACK);
- ret = 1;
- break;
+ return 1;
}
case FL_DRAG: {
- /* here the mouse is on the chanStart tool */
+ /* here the mouse is on the m_chanStart tool */
- if (chanStartLit && pushed) {
+ if (m_chanStartLit && m_pushed) {
- chanStart = Fl::event_x() - x();
+ m_chanStart = snap(m_mouseX);
- if (grid.snap)
- chanStart = applySnap(chanStart);
-
- if (chanStart < 0)
- chanStart = 0;
+ if (m_chanStart < 0)
+ m_chanStart = 0;
else
- if (chanStart >= chanEnd)
- chanStart = chanEnd - 2;
+ if (m_chanStart >= m_chanEnd)
+ m_chanStart = m_chanEnd - 2;
redraw();
}
else
- if (chanEndLit && pushed) {
-
- chanEnd = Fl::event_x() - x();
+ if (m_chanEndLit && m_pushed) {
- if (grid.snap)
- chanEnd = applySnap(chanEnd);
+ m_chanEnd = snap(m_mouseX);
- if (chanEnd > data.size)
- chanEnd = data.size;
+ if (m_chanEnd > m_ch->wave->getSize())
+ m_chanEnd = m_ch->wave->getSize();
else
- if (chanEnd <= chanStart)
- chanEnd = chanStart + 2;
+ if (m_chanEnd <= m_chanStart)
+ m_chanEnd = m_chanStart + 2;
redraw();
}
/* Here the mouse is on the waveform, i.e. a new selection has started. */
else
- if (dragged) {
- selection.bPixel = Fl::event_x() - x();
- if (grid.snap)
- selection.bPixel = applySnap(selection.bPixel);
+ if (m_dragged) {
+ m_selection.b = snap(m_mouseX);
redraw();
}
/* here the mouse is on a selection boundary i.e. resize */
else
- if (resizedA || resizedB) {
- int pos = Fl::event_x() - x();
- if (grid.snap)
- pos = applySnap(pos);
- resizedA ? selection.aPixel = pos : selection.bPixel = pos;
+ if (m_resizedA || m_resizedB) {
+ int pos = snap(m_mouseX);
+ m_resizedA ? m_selection.a = pos : m_selection.b = pos;
redraw();
}
- mouseX = Fl::event_x();
- ret = 1;
- break;
+ return 1;
}
+
+ default:
+ return Fl_Widget::handle(e);
}
- return ret;
}
/* -------------------------------------------------------------------------- */
-int geWaveform::applySnap(int pos)
+int geWaveform::snap(int pos)
{
- /* Pixel snap disances (SNAPPING) must be equal to those defined in
- mouseOnSelectionA() and mouseOnSelectionB(). */
-
- for (unsigned i=0; i<grid.points.size(); i++) {
- if (pos >= grid.points.at(i) - SNAPPING &&
- pos <= grid.points.at(i) + SNAPPING)
- {
- return grid.points.at(i);
- }
- }
- return pos;
+ if (!m_grid.snap)
+ return pos;
+ for (int pf : m_grid.points) {
+ if (pos >= pf - pixelToFrame(SNAPPING) &&
+ pos <= pf + pixelToFrame(SNAPPING))
+ {
+ return pf;
+ }
+ }
+ return pos;
}
bool geWaveform::mouseOnStart()
{
- return mouseX - (FLAG_WIDTH / 2) > chanStart + x() - BORDER &&
- mouseX - (FLAG_WIDTH / 2) <= chanStart + x() - BORDER + FLAG_WIDTH &&
- mouseY > h() + y() - FLAG_HEIGHT;
+ int mouseXp = frameToPixel(m_mouseX);
+ int mouseYp = frameToPixel(m_mouseY);
+ int chanStartP = frameToPixel(m_chanStart);
+ return mouseXp - (FLAG_WIDTH / 2) > chanStartP - BORDER &&
+ mouseXp - (FLAG_WIDTH / 2) <= chanStartP - BORDER + FLAG_WIDTH &&
+ mouseYp > h() - FLAG_HEIGHT;
}
bool geWaveform::mouseOnEnd()
{
- return mouseX - (FLAG_WIDTH / 2) >= chanEnd + x() - BORDER - FLAG_WIDTH &&
- mouseX - (FLAG_WIDTH / 2) <= chanEnd + x() - BORDER &&
- mouseY <= y() + FLAG_HEIGHT + 1;
+ int mouseXp = frameToPixel(m_mouseX);
+ int mouseYp = frameToPixel(m_mouseY);
+ int chanEndP = frameToPixel(m_chanEnd);
+ return mouseXp - (FLAG_WIDTH / 2) >= chanEndP - BORDER - FLAG_WIDTH &&
+ mouseXp - (FLAG_WIDTH / 2) <= chanEndP - BORDER &&
+ mouseYp <= FLAG_HEIGHT + 1;
}
/* -------------------------------------------------------------------------- */
-/* pixel boundaries (10px) must be equal to the snap factor distance
- * defined in geWaveform::applySnap() */
bool geWaveform::mouseOnSelectionA()
{
- return mouseX >= selection.aPixel - (FLAG_WIDTH / 2) + x() &&
- mouseX <= selection.aPixel + (FLAG_WIDTH / 2) + x();
+ int mouseXp = frameToPixel(m_mouseX);
+ int selAp = frameToPixel(m_selection.a);
+ return mouseXp >= selAp - (FLAG_WIDTH / 2) && mouseXp <= selAp + (FLAG_WIDTH / 2);
}
bool geWaveform::mouseOnSelectionB()
{
- return mouseX >= selection.bPixel - (FLAG_WIDTH / 2) + x() &&
- mouseX <= selection.bPixel + (FLAG_WIDTH / 2) + x();
+ int mouseXp = frameToPixel(m_mouseX);
+ int selBp = frameToPixel(m_selection.b);
+ return mouseXp >= selBp - (FLAG_WIDTH / 2) && mouseXp <= selBp + (FLAG_WIDTH / 2);
}
/* -------------------------------------------------------------------------- */
-int geWaveform::absolutePoint(int p)
+int geWaveform::pixelToFrame(int p)
{
if (p <= 0)
return 0;
-
- if (p > data.size)
- return chan->wave->size / 2;
-
- return (p * ratio) / 2;
+ if (p > m_data.size)
+ return m_ch->wave->getSize() - 1;
+ return p * m_ratio;
}
/* -------------------------------------------------------------------------- */
-int geWaveform::relativePoint(int p)
+int geWaveform::frameToPixel(int p)
{
- return (ceil(p / ratio)) * 2;
+ return ceil(p / m_ratio);
}
void geWaveform::fixSelection()
{
- if (selection.aPixel > selection.bPixel) // inverted selection
- std::swap(selection.aPixel, selection.bPixel);
- selection.aFrame = absolutePoint(selection.aPixel);
- selection.bFrame = absolutePoint(selection.bPixel);
+ if (m_selection.a > m_selection.b) // inverted m_selection
+ std::swap(m_selection.a, m_selection.b);
+ sampleEditor::setPlayHead(m_ch, m_selection.a);
}
void geWaveform::clearSel()
{
- selection.aPixel = 0;
- selection.bPixel = 0;
- selection.aFrame = 0;
- selection.bFrame = 0;
+ m_selection.a = 0;
+ m_selection.b = 0;
}
void geWaveform::setZoom(int type)
{
- int newSize = type == ZOOM_IN ? data.size * 2 : data.size / 2;
-
- if (!alloc(newSize))
+ if (!alloc(type == ZOOM_IN ? m_data.size * 2 : m_data.size / 2))
return;
- size(newSize, h());
+ size(m_data.size, h());
- /* zoom to pointer */
+ /* Zoom to cursor. */
+
+ int newX = -frameToPixel(m_mouseX) + Fl::event_x();
+ if (newX > BORDER)
+ newX = BORDER;
+ position(newX, y());
- int shift;
- if (x() > 0)
- shift = Fl::event_x() - x();
- else
- if (type == ZOOM_IN)
- shift = Fl::event_x() + abs(x());
- else
- shift = (Fl::event_x() + abs(x())) / -2;
-
- if (x() - shift > BORDER)
- shift = 0;
+ /* Avoid overflow when zooming out with scrollbar like that:
- position(x() - shift, y());
+ |----------[scrollbar]|
+ Offset vs smaller:
+
+ |[wave------------| offset > 0 smaller = false
+ |[wave----] | offset < 0, smaller = true
+ |-------------] | offset < 0, smaller = false */
- /* avoid overflow when zooming out with scrollbar like that:
- * |----------[scrollbar]|
- *
- * offset vs smaller:
- * |[wave------------| offset > 0 smaller = false
- * |[wave----] | offset < 0, smaller = true
- * |-------------] | offset < 0, smaller = false */
-
- int parentW = parent()->w();
- int thisW = x() + w() - BORDER; // visible width, not full width
+ int parentW = parent()->w();
+ int thisW = x() + w() - BORDER; // visible width, not full width
if (thisW < parentW)
position(x() + parentW - thisW, y());
void geWaveform::refresh()
{
- alloc(data.size);
+ alloc(m_data.size, true); // force
redraw();
}
void geWaveform::setGridLevel(int l)
{
- grid.points.clear();
- grid.level = l;
- alloc(data.size);
+ m_grid.points.clear();
+ m_grid.level = l;
+ alloc(m_data.size, true); // force alloc
redraw();
}
bool geWaveform::isSelected()
{
- return selection.aPixel != selection.bPixel;
+ return m_selection.a != m_selection.b;
}
/* -------------------------------------------------------------------------- */
+void geWaveform::setSnap(bool v) { m_grid.snap = v; }
+bool geWaveform::getSnap() { return m_grid.snap; }
+int geWaveform::getSize() { return m_data.size; }
+
+
+/* -------------------------------------------------------------------------- */
+
+
int geWaveform::getSelectionA()
{
- return selection.aFrame;
+ return m_selection.a;
}
int geWaveform::getSelectionB()
{
- return selection.bFrame;
+ return m_selection.b;
}
static const int FLAG_WIDTH = 20;
static const int FLAG_HEIGHT = 20;
static const int BORDER = 8; // window border <-> widget border
- static const int SNAPPING = 10;
+ static const int SNAPPING = 16;
/* selection
- Portion of the selected wave, in pixel and in frames. */
+ Portion of the selected wave, in frames. */
struct
{
- int aPixel;
- int bPixel;
- int aFrame;
- int bFrame;
- } selection;
+ int a;
+ int b;
+ } m_selection;
/* data
Real graphic stuff from the underlying waveform. */
struct
{
int* sup; // upper part of the waveform
- int* inf; // lower "" "" "" ""
+ int* inf; // lower part of the waveform
int size; // width of the waveform to draw (in pixel)
- } data;
+ } m_data;
struct
{
bool snap;
int level;
std::vector<int> points;
- } grid;
-
- SampleChannel* chan;
+ } m_grid;
+
+ SampleChannel* m_ch;
+ int m_chanStart;
+ bool m_chanStartLit;
+ int m_chanEnd;
+ bool m_chanEndLit;
+ bool m_pushed;
+ bool m_dragged;
+ bool m_resizedA;
+ bool m_resizedB;
+ float m_ratio;
+ int m_mouseX;
+ int m_mouseY;
/* mouseOnStart/end
Is mouse on start or end flag? */
bool mouseOnSelectionA();
bool mouseOnSelectionB();
- /* absolutePoint
- From a relative 'p' point (zoom affected) returns the same point zoom 1:1
- based. */
-
- int absolutePoint(int p);
-
- /* relativePoint
- From an absolute 'p' point (1:1 zoom) returns the same point zoom affected. */
-
- int relativePoint(int p);
+ int pixelToFrame(int p);
+ int frameToPixel(int f);
/* fixSelection
Helper function which flattens the selection if it was made from right to left
bool smaller();
- /* applySnap
- Snap a point at 'pos' pixel. */
+ /* snap
+ Snaps a point at 'pos' pixel. */
- int applySnap(int pos);
+ int snap(int pos);
/* draw*
Drawing functions. */
int handle(int e) override;
/* alloc
- * allocate memory for the picture */
+ Allocates memory for the picture. It's smart enough not to reallocate if
+ datasize hasn't changed, but it can be forced otherwise. */
- int alloc(int datasize=0);
+ int alloc(int datasize, bool force=false);
/* recalcPoints
- * re-calc chanStart, chanEnd, ... */
+ * re-calc m_chanStart, m_chanEnd, ... */
void recalcPoints();
void setGridLevel(int l);
- void setSnap(bool v) { grid.snap = v; }
- bool getSnap() { return grid.snap; }
-
- int getSize() { return data.size; }
+ void setSnap(bool v);
+ bool getSnap();
+ int getSize();
/* isSelected
Tells whether a portion of the waveform has been selected. */
Removes any active selection. */
void clearSel();
-
- int chanStart;
- bool chanStartLit;
- int chanEnd;
- bool chanEndLit;
- bool pushed;
- bool dragged;
- bool resizedA;
- bool resizedB;
- float ratio;
- int mouseX;
- int mouseY;
};
/* If Sample Editor is open, repaint it (for dynamic play head). */
gdSampleEditor* se = static_cast<gdSampleEditor*>(gu_getSubwindow(G_MainWin, WID_SAMPLE_EDITOR));
- if (se)
+ if (se != nullptr)
se->waveTools->redrawWaveformAsync();
/* redraw GUI */
#include <climits>
#include <sstream>
+#include "../core/const.h"
#include "string.h"
{
string out = "";
-#if defined(__linux__) || defined(__APPLE__)
+#if defined(G_OS_LINUX) || defined(G_OS_MAC)
char *buf = realpath(path.c_str(), nullptr);
/* -------------------------------------------------------------------------- */
-string gu_itoa(int i)
+string gu_toString(int i)
{
- // TODO - use std::to_string -> http://stackoverflow.com/questions/191757/how-to-concatenate-a-stdstring-and-an-int?rq=1
+ /* std::to_string is the way to go. Unfortunately it seems that it isn't
+ available in gcc's standard library (libstdc++), it is however, available in
+ libc++ which comes with LLVM/clang. */
+
+#ifdef G_OS_MAC
+
std::stringstream out;
out << i;
return out.str();
+
+#else
+
+ return std::to_string(i);
+
+#endif
}
size_t curr = 0;
size_t next = -1;
do {
- curr = next + 1;
- next = full.find_first_of(sep, curr);
+ curr = next + 1;
+ next = full.find_first_of(sep, curr);
token = full.substr(curr, next - curr);
if (token != "")
v->push_back(token);
std::string gu_trim(const std::string &s);
-// TODO - use std::to_string -> http://stackoverflow.com/questions/191757/how-to-concatenate-a-stdstring-and-an-int?rq=1
-std::string gu_itoa(int i);
+std::string gu_toString(int i);
void gu_split(std::string in, std::string sep, std::vector<std::string> *v);
{
REQUIRE(gu_replace("Giada is cool", "cool", "hot") == "Giada is hot");
REQUIRE(gu_trim(" Giada is cool ") == "Giada is cool");
- REQUIRE(gu_itoa(666) == "666");
+ REQUIRE(gu_toString(666) == "666");
vector<std::string> v;
gu_split("Giada is cool", " ", &v);
+#include <memory>
#include "../src/core/wave.h"
#include "catch/single_include/catch.hpp"
using std::string;
-#define G_SAMPLE_RATE 44100
-#define G_BUFFER_SIZE 4096
-
-
TEST_CASE("Test Wave class")
{
- Wave w1;
+ static const int SAMPLE_RATE = 44100;
+ static const int BUFFER_SIZE = 4096;
+ static const int CHANNELS = 2;
+ static const int BIT_DEPTH = 32;
- SECTION("test read & write")
+ /* Each SECTION the TEST_CASE is executed from the start. Any code between
+ this comment and the first SECTION macro is exectuted before each SECTION. */
+
+ std::unique_ptr<Wave> wave;
+
+ SECTION("test basename")
{
- REQUIRE(w1.open("tests/resources/test.wav") == 1);
- REQUIRE(w1.readData() == 1);
- REQUIRE(w1.rate() == 44100);
- REQUIRE(w1.channels() == 1);
- REQUIRE(w1.basename() == "test");
- REQUIRE(w1.extension() == "wav");
- REQUIRE(w1.writeData("test-write.wav") == true);
- }
+ wave = std::unique_ptr<Wave>(new Wave(nullptr, BUFFER_SIZE, CHANNELS,
+ SAMPLE_RATE, BIT_DEPTH, "path/to/sample.wav"));
- SECTION("test copy constructor")
+ REQUIRE(wave->getPath() == "path/to/sample.wav");
+ REQUIRE(wave->getBasename() == "sample");
+ REQUIRE(wave->getBasename(true) == "sample.wav");
+ }
+
+ SECTION("test change name")
{
- Wave w2(w1);
- REQUIRE(w2.size == w1.size);
- REQUIRE(w2.isLogical == true);
- //REQUIRE(w2.rate() == 44100); // WHAT THE FUCK???
- REQUIRE(w2.channels() == 1);
- REQUIRE(w2.writeData("test-write.wav") == true);
+ wave = std::unique_ptr<Wave>(new Wave(nullptr, BUFFER_SIZE, CHANNELS,
+ SAMPLE_RATE, BIT_DEPTH, "path/to/sample.wav"));
+ wave->setName("waveform");
+
+ REQUIRE(wave->getPath() == "path/to/waveform.wav");
+ REQUIRE(wave->getBasename() == "waveform");
+ REQUIRE(wave->getBasename(true) == "waveform.wav");
}
- SECTION("test rec")
+ SECTION("test memory cleanup")
{
- Wave w3;
- REQUIRE(w3.allocEmpty(G_BUFFER_SIZE, G_SAMPLE_RATE) == 1);
- REQUIRE(w3.rate() == G_SAMPLE_RATE);
- REQUIRE(w3.channels() == 2);
- REQUIRE(w3.writeData("test-write.wav") == true);
+ float* data = new float[BUFFER_SIZE];
+
+ wave = std::unique_ptr<Wave>(new Wave(data, BUFFER_SIZE, CHANNELS,
+ SAMPLE_RATE, BIT_DEPTH, "path/to/sample.wav"));
+ wave->clear();
+
+ REQUIRE(wave->getData() == nullptr);
+ REQUIRE(wave->getPath() == "");
+ REQUIRE(wave->getSize() == 0);
}
}
--- /dev/null
+#include <memory>
+#include "../src/core/const.h"
+#include "../src/core/wave.h"
+#include "../src/core/waveFx.h"
+#include "catch/single_include/catch.hpp"
+
+
+using std::string;
+using namespace giada::m;
+
+
+TEST_CASE("Test waveFx")
+{
+ static const int SAMPLE_RATE = 44100;
+ static const int BUFFER_SIZE = 4000;
+ static const int BIT_DEPTH = 32;
+
+ std::unique_ptr<Wave> waveMono = std::unique_ptr<Wave>(new Wave(new float[BUFFER_SIZE],
+ BUFFER_SIZE, 1, SAMPLE_RATE, BIT_DEPTH, "path/to/sample.wav"));
+
+ std::unique_ptr<Wave> waveStereo = std::unique_ptr<Wave>(new Wave(new float[BUFFER_SIZE * 2],
+ BUFFER_SIZE, 2, SAMPLE_RATE, BIT_DEPTH, "path/to/sample.wav"));
+
+ SECTION("test mono->stereo conversion")
+ {
+ int prevSize = waveMono->getSize();
+
+ REQUIRE(wfx::monoToStereo(waveMono.get()) == G_RES_OK);
+ REQUIRE(waveMono->getSize() == prevSize); // size does not change, channels do
+ REQUIRE(waveMono->getChannels() == 2);
+
+ SECTION("test mono->stereo conversion for already stereo wave")
+ {
+ /* Should do nothing. */
+ int prevSize = waveStereo->getSize();
+
+ REQUIRE(wfx::monoToStereo(waveStereo.get()) == G_RES_OK);
+ REQUIRE(waveStereo->getSize() == prevSize);
+ REQUIRE(waveStereo->getChannels() == 2);
+ }
+ }
+
+ SECTION("test silence")
+ {
+ int a = 20;
+ int b = 57;
+ wfx::silence(waveStereo.get(), a, b);
+
+ for (int i=a; i<b; i++) {
+ float* frame = waveStereo->getFrame(i);
+ for (int k=0; k<waveStereo->getChannels(); k++)
+ REQUIRE(frame[k] == 0.0f);
+ }
+
+ SECTION("test silence (mono)")
+ {
+ wfx::silence(waveMono.get(), a, b);
+
+ for (int i=a; i<b; i++) {
+ float* frame = waveMono->getFrame(i);
+ for (int k=0; k<waveMono->getChannels(); k++)
+ REQUIRE(frame[k] == 0.0f);
+ }
+ }
+ }
+
+ SECTION("test cut")
+ {
+ int a = 47;
+ int b = 210;
+ int range = b - a;
+ int prevSize = waveStereo->getSize();
+
+ REQUIRE(wfx::cut(waveStereo.get(), a, b) == G_RES_OK);
+ REQUIRE(waveStereo->getSize() == prevSize - range);
+
+ SECTION("test cut (mono)")
+ {
+ prevSize = waveMono->getSize();
+ REQUIRE(wfx::cut(waveMono.get(), a, b) == G_RES_OK);
+ REQUIRE(waveMono->getSize() == prevSize - range);
+ }
+ }
+
+ SECTION("test trim")
+ {
+ int a = 47;
+ int b = 210;
+ int area = b - a;
+
+ REQUIRE(wfx::trim(waveStereo.get(), a, b) == G_RES_OK);
+ REQUIRE(waveStereo->getSize() == area);
+
+ SECTION("test trim (mono)")
+ {
+ REQUIRE(wfx::trim(waveMono.get(), a, b) == G_RES_OK);
+ REQUIRE(waveMono->getSize() == area);
+ }
+ }
+
+ SECTION("test fade")
+ {
+ int a = 47;
+ int b = 500;
+
+ wfx::fade(waveStereo.get(), a, b, wfx::FADE_IN);
+ wfx::fade(waveStereo.get(), a, b, wfx::FADE_OUT);
+
+ REQUIRE(waveStereo->getFrame(a)[0] == 0.0f);
+ REQUIRE(waveStereo->getFrame(a)[1] == 0.0f);
+ REQUIRE(waveStereo->getFrame(b)[0] == 0.0f);
+ REQUIRE(waveStereo->getFrame(b)[1] == 0.0f);
+
+ SECTION("test fade (mono)")
+ {
+ wfx::fade(waveMono.get(), a, b, wfx::FADE_IN);
+ wfx::fade(waveMono.get(), a, b, wfx::FADE_OUT);
+
+ REQUIRE(waveMono->getFrame(a)[0] == 0.0f);
+ REQUIRE(waveMono->getFrame(b)[0] == 0.0f);
+ }
+ }
+
+ SECTION("test smooth")
+ {
+ int a = 11;
+ int b = 79;
+
+ wfx::smooth(waveStereo.get(), a, b);
+
+ REQUIRE(waveStereo->getFrame(a)[0] == 0.0f);
+ REQUIRE(waveStereo->getFrame(a)[1] == 0.0f);
+ REQUIRE(waveStereo->getFrame(b)[0] == 0.0f);
+ REQUIRE(waveStereo->getFrame(b)[1] == 0.0f);
+
+ SECTION("test smooth (mono)")
+ {
+ wfx::smooth(waveMono.get(), a, b);
+ REQUIRE(waveMono->getFrame(a)[0] == 0.0f);
+ REQUIRE(waveMono->getFrame(b)[0] == 0.0f);
+ }
+ }
+}
--- /dev/null
+#include <memory>
+#include "../src/core/waveManager.h"
+#include "../src/core/wave.h"
+#include "../src/core/const.h"
+#include "catch/single_include/catch.hpp"
+
+
+using std::string;
+using namespace giada::m;
+
+
+#define G_SAMPLE_RATE 44100
+#define G_BUFFER_SIZE 4096
+#define G_CHANNELS 2
+
+
+TEST_CASE("Test waveManager")
+{
+ /* Each SECTION the TEST_CASE is executed from the start. Any code between
+ this comment and the first SECTION macro is exectuted before each SECTION. */
+
+ Wave* w;
+
+ SECTION("test creation")
+ {
+ int res = waveManager::create("tests/resources/test.wav", &w);
+ std::unique_ptr<Wave> wave(w);
+
+ REQUIRE(res == G_RES_OK);
+ REQUIRE(wave->getRate() == G_SAMPLE_RATE);
+ REQUIRE(wave->getChannels() == G_CHANNELS);
+ REQUIRE(wave->isLogical() == false);
+ REQUIRE(wave->isEdited() == false);
+ }
+
+ SECTION("test recording")
+ {
+ int res = waveManager::createEmpty(G_BUFFER_SIZE, G_SAMPLE_RATE,
+ "test.wav", &w);
+ std::unique_ptr<Wave> wave(w);
+
+ REQUIRE(res == G_RES_OK);
+ REQUIRE(wave->getRate() == G_SAMPLE_RATE);
+ REQUIRE(wave->getSize() == G_BUFFER_SIZE / wave->getChannels());
+ REQUIRE(wave->getChannels() == G_CHANNELS);
+ REQUIRE(wave->isLogical() == true);
+ REQUIRE(wave->isEdited() == false);
+ }
+
+ SECTION("test resampling")
+ {
+ int res = waveManager::create("tests/resources/test.wav", &w);
+ std::unique_ptr<Wave> wave(w);
+
+ REQUIRE(res == G_RES_OK);
+
+ int oldSize = wave->getSize();
+ res = waveManager::resample(wave.get(), 1, G_SAMPLE_RATE * 2);
+
+ REQUIRE(res == G_RES_OK);
+ REQUIRE(wave->getRate() == G_SAMPLE_RATE * 2);
+ REQUIRE(wave->getSize() == oldSize * 2);
+ REQUIRE(wave->getChannels() == G_CHANNELS);
+ REQUIRE(wave->isLogical() == false);
+ REQUIRE(wave->isEdited() == false);
+ }
+}