1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-13 00:04:19 +00:00

Added AudioAppExample file in examples

This commit is contained in:
Felix Faire 2014-10-30 12:59:58 +00:00
parent 06b9bdefb6
commit c81ee3b5be
1140 changed files with 442849 additions and 10 deletions

View file

@ -0,0 +1,50 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE 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.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
AudioAppComponent::AudioAppComponent()
{
}
AudioAppComponent::~AudioAppComponent()
{
// If you hit this then your derived class must call shutdown audio in
// destructor!
jassert (audioSourcePlayer.getCurrentSource() == nullptr);
}
void AudioAppComponent::setAudioChannels (int numInputChannels, int numOutputChannels)
{
String audioError = deviceManager.initialise (numInputChannels, numOutputChannels, nullptr, true);
jassert (audioError.isEmpty());
deviceManager.addAudioCallback (&audioSourcePlayer);
audioSourcePlayer.setSource (this);
}
void AudioAppComponent::shutdownAudio()
{
audioSourcePlayer.setSource (nullptr);
deviceManager.removeAudioCallback (&audioSourcePlayer);
deviceManager.closeAudioDevice();
}

View file

@ -0,0 +1,117 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE 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.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_AUDIOAPPCOMPONENT_H_INCLUDED
#define JUCE_AUDIOAPPCOMPONENT_H_INCLUDED
//==============================================================================
/**
A base class for writing audio apps that stream from the audio i/o devices.
A subclass can inherit from this and implement just a few methods such as
renderAudio(). The base class provides a basic AudioDeviceManager object
and runs audio through the
*/
class AudioAppComponent : public Component,
public AudioSource
{
public:
AudioAppComponent();
~AudioAppComponent();
/** A subclass should call this from their constructor, to set up the audio. */
void setAudioChannels (int numInputChannels, int numOutputChannels);
/** Tells the source to prepare for playing.
An AudioSource has two states: prepared and unprepared.
The prepareToPlay() method is guaranteed to be called at least once on an 'unpreprared'
source to put it into a 'prepared' state before any calls will be made to getNextAudioBlock().
This callback allows the source to initialise any resources it might need when playing.
Once playback has finished, the releaseResources() method is called to put the stream
back into an 'unprepared' state.
Note that this method could be called more than once in succession without
a matching call to releaseResources(), so make sure your code is robust and
can handle that kind of situation.
@param samplesPerBlockExpected the number of samples that the source
will be expected to supply each time its
getNextAudioBlock() method is called. This
number may vary slightly, because it will be dependent
on audio hardware callbacks, and these aren't
guaranteed to always use a constant block size, so
the source should be able to cope with small variations.
@param sampleRate the sample rate that the output will be used at - this
is needed by sources such as tone generators.
@see releaseResources, getNextAudioBlock
*/
virtual void prepareToPlay (int samplesPerBlockExpected,
double sampleRate) = 0;
/** Allows the source to release anything it no longer needs after playback has stopped.
This will be called when the source is no longer going to have its getNextAudioBlock()
method called, so it should release any spare memory, etc. that it might have
allocated during the prepareToPlay() call.
Note that there's no guarantee that prepareToPlay() will actually have been called before
releaseResources(), and it may be called more than once in succession, so make sure your
code is robust and doesn't make any assumptions about when it will be called.
@see prepareToPlay, getNextAudioBlock
*/
virtual void releaseResources() = 0;
/** Called repeatedly to fetch subsequent blocks of audio data.
After calling the prepareToPlay() method, this callback will be made each
time the audio playback hardware (or whatever other destination the audio
data is going to) needs another block of data.
It will generally be called on a high-priority system thread, or possibly even
an interrupt, so be careful not to do too much work here, as that will cause
audio glitches!
@see AudioSourceChannelInfo, prepareToPlay, releaseResources
*/
virtual void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) = 0;
void shutdownAudio();
AudioDeviceManager deviceManager;
private:
//==============================================================================
AudioSourcePlayer audioSourcePlayer;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioAppComponent)
};
#endif // JUCE_AUDIOAPPCOMPONENT_H_INCLUDED

View file

@ -0,0 +1,113 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE 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.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_AUDIODEVICESELECTORCOMPONENT_H_INCLUDED
#define JUCE_AUDIODEVICESELECTORCOMPONENT_H_INCLUDED
//==============================================================================
/**
A component containing controls to let the user change the audio settings of
an AudioDeviceManager object.
Very easy to use - just create one of these and show it to the user.
@see AudioDeviceManager
*/
class JUCE_API AudioDeviceSelectorComponent : public Component,
private ComboBoxListener, // (can't use ComboBox::Listener due to idiotic VC2005 bug)
private ChangeListener
{
public:
//==============================================================================
/** Creates the component.
If your app needs only output channels, you might ask for a maximum of 0 input
channels, and the component won't display any options for choosing the input
channels. And likewise if you're doing an input-only app.
@param deviceManager the device manager that this component should control
@param minAudioInputChannels the minimum number of audio input channels that the application needs
@param maxAudioInputChannels the maximum number of audio input channels that the application needs
@param minAudioOutputChannels the minimum number of audio output channels that the application needs
@param maxAudioOutputChannels the maximum number of audio output channels that the application needs
@param showMidiInputOptions if true, the component will allow the user to select which midi inputs are enabled
@param showMidiOutputSelector if true, the component will let the user choose a default midi output device
@param showChannelsAsStereoPairs if true, channels will be treated as pairs; if false, channels will be
treated as a set of separate mono channels.
@param hideAdvancedOptionsWithButton if true, only the minimum amount of UI components
are shown, with an "advanced" button that shows the rest of them
*/
AudioDeviceSelectorComponent (AudioDeviceManager& deviceManager,
int minAudioInputChannels,
int maxAudioInputChannels,
int minAudioOutputChannels,
int maxAudioOutputChannels,
bool showMidiInputOptions,
bool showMidiOutputSelector,
bool showChannelsAsStereoPairs,
bool hideAdvancedOptionsWithButton);
/** Destructor */
~AudioDeviceSelectorComponent();
/** The device manager that this component is controlling */
AudioDeviceManager& deviceManager;
/** Sets the standard height used for items in the panel. */
void setItemHeight (int itemHeight);
/** Returns the standard height used for items in the panel. */
int getItemHeight() const noexcept { return itemHeight; }
//==============================================================================
/** @internal */
void resized() override;
private:
//==============================================================================
ScopedPointer<ComboBox> deviceTypeDropDown;
ScopedPointer<Label> deviceTypeDropDownLabel;
ScopedPointer<Component> audioDeviceSettingsComp;
String audioDeviceSettingsCompType;
int itemHeight;
const int minOutputChannels, maxOutputChannels, minInputChannels, maxInputChannels;
const bool showChannelsAsStereoPairs;
const bool hideAdvancedOptionsWithButton;
class MidiInputSelectorComponentListBox;
friend struct ContainerDeletePolicy<MidiInputSelectorComponentListBox>;
ScopedPointer<MidiInputSelectorComponentListBox> midiInputsList;
ScopedPointer<ComboBox> midiOutputSelector;
ScopedPointer<Label> midiInputsLabel, midiOutputLabel;
void comboBoxChanged (ComboBox*) override;
void changeListenerCallback (ChangeBroadcaster*) override;
void updateAllControls();
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioDeviceSelectorComponent)
};
#endif // JUCE_AUDIODEVICESELECTORCOMPONENT_H_INCLUDED

View file

@ -0,0 +1,820 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE 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.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
struct AudioThumbnail::MinMaxValue
{
MinMaxValue() noexcept
{
values[0] = 0;
values[1] = 0;
}
inline void set (const char newMin, const char newMax) noexcept
{
values[0] = newMin;
values[1] = newMax;
}
inline char getMinValue() const noexcept { return values[0]; }
inline char getMaxValue() const noexcept { return values[1]; }
inline void setFloat (Range<float> newRange) noexcept
{
values[0] = (char) jlimit (-128, 127, roundFloatToInt (newRange.getStart() * 127.0f));
values[1] = (char) jlimit (-128, 127, roundFloatToInt (newRange.getEnd() * 127.0f));
if (values[0] == values[1])
{
if (values[1] == 127)
values[0]--;
else
values[1]++;
}
}
inline bool isNonZero() const noexcept
{
return values[1] > values[0];
}
inline int getPeak() const noexcept
{
return jmax (std::abs ((int) values[0]),
std::abs ((int) values[1]));
}
inline void read (InputStream& input) { input.read (values, 2); }
inline void write (OutputStream& output) { output.write (values, 2); }
private:
char values[2];
};
//==============================================================================
class AudioThumbnail::LevelDataSource : public TimeSliceClient
{
public:
LevelDataSource (AudioThumbnail& thumb, AudioFormatReader* newReader, int64 hash)
: lengthInSamples (0), numSamplesFinished (0), sampleRate (0), numChannels (0),
hashCode (hash), owner (thumb), reader (newReader), lastReaderUseTime (0)
{
}
LevelDataSource (AudioThumbnail& thumb, InputSource* src)
: lengthInSamples (0), numSamplesFinished (0), sampleRate (0), numChannels (0),
hashCode (src->hashCode()), owner (thumb), source (src), lastReaderUseTime (0)
{
}
~LevelDataSource()
{
owner.cache.getTimeSliceThread().removeTimeSliceClient (this);
}
enum { timeBeforeDeletingReader = 3000 };
void initialise (int64 samplesFinished)
{
const ScopedLock sl (readerLock);
numSamplesFinished = samplesFinished;
createReader();
if (reader != nullptr)
{
lengthInSamples = reader->lengthInSamples;
numChannels = reader->numChannels;
sampleRate = reader->sampleRate;
if (lengthInSamples <= 0 || isFullyLoaded())
reader = nullptr;
else
owner.cache.getTimeSliceThread().addTimeSliceClient (this);
}
}
void getLevels (int64 startSample, int numSamples, Array<Range<float> >& levels)
{
const ScopedLock sl (readerLock);
if (reader == nullptr)
{
createReader();
if (reader != nullptr)
{
lastReaderUseTime = Time::getMillisecondCounter();
owner.cache.getTimeSliceThread().addTimeSliceClient (this);
}
}
if (reader != nullptr)
{
if (levels.size() < (int) reader->numChannels)
levels.insertMultiple (0, Range<float>(), (int) (reader->numChannels - levels.size()));
reader->readMaxLevels (startSample, numSamples, levels.getRawDataPointer(), (int) reader->numChannels);
lastReaderUseTime = Time::getMillisecondCounter();
}
}
void releaseResources()
{
const ScopedLock sl (readerLock);
reader = nullptr;
}
int useTimeSlice() override
{
if (isFullyLoaded())
{
if (reader != nullptr && source != nullptr)
{
if (Time::getMillisecondCounter() > lastReaderUseTime + timeBeforeDeletingReader)
releaseResources();
else
return 200;
}
return -1;
}
bool justFinished = false;
{
const ScopedLock sl (readerLock);
createReader();
if (reader != nullptr)
{
if (! readNextBlock())
return 0;
justFinished = true;
}
}
if (justFinished)
owner.cache.storeThumb (owner, hashCode);
return 200;
}
bool isFullyLoaded() const noexcept
{
return numSamplesFinished >= lengthInSamples;
}
inline int sampleToThumbSample (const int64 originalSample) const noexcept
{
return (int) (originalSample / owner.samplesPerThumbSample);
}
int64 lengthInSamples, numSamplesFinished;
double sampleRate;
unsigned int numChannels;
int64 hashCode;
private:
AudioThumbnail& owner;
ScopedPointer<InputSource> source;
ScopedPointer<AudioFormatReader> reader;
CriticalSection readerLock;
uint32 lastReaderUseTime;
void createReader()
{
if (reader == nullptr && source != nullptr)
if (InputStream* audioFileStream = source->createInputStream())
reader = owner.formatManagerToUse.createReaderFor (audioFileStream);
}
bool readNextBlock()
{
jassert (reader != nullptr);
if (! isFullyLoaded())
{
const int numToDo = (int) jmin (256 * (int64) owner.samplesPerThumbSample, lengthInSamples - numSamplesFinished);
if (numToDo > 0)
{
int64 startSample = numSamplesFinished;
const int firstThumbIndex = sampleToThumbSample (startSample);
const int lastThumbIndex = sampleToThumbSample (startSample + numToDo);
const int numThumbSamps = lastThumbIndex - firstThumbIndex;
HeapBlock<MinMaxValue> levelData ((size_t) numThumbSamps * numChannels);
HeapBlock<MinMaxValue*> levels (numChannels);
for (int i = 0; i < (int) numChannels; ++i)
levels[i] = levelData + i * numThumbSamps;
HeapBlock<Range<float> > levelsRead (numChannels);
for (int i = 0; i < numThumbSamps; ++i)
{
reader->readMaxLevels ((firstThumbIndex + i) * owner.samplesPerThumbSample,
owner.samplesPerThumbSample, levelsRead, numChannels);
for (int j = 0; j < (int) numChannels; ++j)
levels[j][i].setFloat (levelsRead[j]);
}
{
const ScopedUnlock su (readerLock);
owner.setLevels (levels, firstThumbIndex, (int) numChannels, numThumbSamps);
}
numSamplesFinished += numToDo;
lastReaderUseTime = Time::getMillisecondCounter();
}
}
return isFullyLoaded();
}
};
//==============================================================================
class AudioThumbnail::ThumbData
{
public:
ThumbData (const int numThumbSamples)
: peakLevel (-1)
{
ensureSize (numThumbSamples);
}
inline MinMaxValue* getData (const int thumbSampleIndex) noexcept
{
jassert (thumbSampleIndex < data.size());
return data.getRawDataPointer() + thumbSampleIndex;
}
int getSize() const noexcept
{
return data.size();
}
void getMinMax (int startSample, int endSample, MinMaxValue& result) const noexcept
{
if (startSample >= 0)
{
endSample = jmin (endSample, data.size() - 1);
char mx = -128;
char mn = 127;
while (startSample <= endSample)
{
const MinMaxValue& v = data.getReference (startSample);
if (v.getMinValue() < mn) mn = v.getMinValue();
if (v.getMaxValue() > mx) mx = v.getMaxValue();
++startSample;
}
if (mn <= mx)
{
result.set (mn, mx);
return;
}
}
result.set (1, 0);
}
void write (const MinMaxValue* const values, const int startIndex, const int numValues)
{
resetPeak();
if (startIndex + numValues > data.size())
ensureSize (startIndex + numValues);
MinMaxValue* const dest = getData (startIndex);
for (int i = 0; i < numValues; ++i)
dest[i] = values[i];
}
void resetPeak() noexcept
{
peakLevel = -1;
}
int getPeak() noexcept
{
if (peakLevel < 0)
{
for (int i = 0; i < data.size(); ++i)
{
const int peak = data[i].getPeak();
if (peak > peakLevel)
peakLevel = peak;
}
}
return peakLevel;
}
private:
Array<MinMaxValue> data;
int peakLevel;
void ensureSize (const int thumbSamples)
{
const int extraNeeded = thumbSamples - data.size();
if (extraNeeded > 0)
data.insertMultiple (-1, MinMaxValue(), extraNeeded);
}
};
//==============================================================================
class AudioThumbnail::CachedWindow
{
public:
CachedWindow()
: cachedStart (0), cachedTimePerPixel (0),
numChannelsCached (0), numSamplesCached (0),
cacheNeedsRefilling (true)
{
}
void invalidate()
{
cacheNeedsRefilling = true;
}
void drawChannel (Graphics& g, const Rectangle<int>& area,
const double startTime, const double endTime,
const int channelNum, const float verticalZoomFactor,
const double rate, const int numChans, const int sampsPerThumbSample,
LevelDataSource* levelData, const OwnedArray<ThumbData>& chans)
{
if (refillCache (area.getWidth(), startTime, endTime, rate,
numChans, sampsPerThumbSample, levelData, chans)
&& isPositiveAndBelow (channelNum, numChannelsCached))
{
const Rectangle<int> clip (g.getClipBounds().getIntersection (area.withWidth (jmin (numSamplesCached, area.getWidth()))));
if (! clip.isEmpty())
{
const float topY = (float) area.getY();
const float bottomY = (float) area.getBottom();
const float midY = (topY + bottomY) * 0.5f;
const float vscale = verticalZoomFactor * (bottomY - topY) / 256.0f;
const MinMaxValue* cacheData = getData (channelNum, clip.getX() - area.getX());
RectangleList<float> waveform;
waveform.ensureStorageAllocated (clip.getWidth());
float x = (float) clip.getX();
for (int w = clip.getWidth(); --w >= 0;)
{
if (cacheData->isNonZero())
{
const float top = jmax (midY - cacheData->getMaxValue() * vscale - 0.3f, topY);
const float bottom = jmin (midY - cacheData->getMinValue() * vscale + 0.3f, bottomY);
waveform.addWithoutMerging (Rectangle<float> (x, top, 1.0f, bottom - top));
}
x += 1.0f;
++cacheData;
}
g.fillRectList (waveform);
}
}
}
private:
Array<MinMaxValue> data;
double cachedStart, cachedTimePerPixel;
int numChannelsCached, numSamplesCached;
bool cacheNeedsRefilling;
bool refillCache (const int numSamples, double startTime, const double endTime,
const double rate, const int numChans, const int sampsPerThumbSample,
LevelDataSource* levelData, const OwnedArray<ThumbData>& chans)
{
const double timePerPixel = (endTime - startTime) / numSamples;
if (numSamples <= 0 || timePerPixel <= 0.0 || rate <= 0)
{
invalidate();
return false;
}
if (numSamples == numSamplesCached
&& numChannelsCached == numChans
&& startTime == cachedStart
&& timePerPixel == cachedTimePerPixel
&& ! cacheNeedsRefilling)
{
return ! cacheNeedsRefilling;
}
numSamplesCached = numSamples;
numChannelsCached = numChans;
cachedStart = startTime;
cachedTimePerPixel = timePerPixel;
cacheNeedsRefilling = false;
ensureSize (numSamples);
if (timePerPixel * rate <= sampsPerThumbSample && levelData != nullptr)
{
int sample = roundToInt (startTime * rate);
Array<Range<float> > levels;
int i;
for (i = 0; i < numSamples; ++i)
{
const int nextSample = roundToInt ((startTime + timePerPixel) * rate);
if (sample >= 0)
{
if (sample >= levelData->lengthInSamples)
{
for (int chan = 0; chan < numChannelsCached; ++chan)
*getData (chan, i) = MinMaxValue();
}
else
{
levelData->getLevels (sample, jmax (1, nextSample - sample), levels);
const int totalChans = jmin (levels.size(), numChannelsCached);
for (int chan = 0; chan < totalChans; ++chan)
getData (chan, i)->setFloat (levels.getReference (chan));
}
}
startTime += timePerPixel;
sample = nextSample;
}
numSamplesCached = i;
}
else
{
jassert (chans.size() == numChannelsCached);
for (int channelNum = 0; channelNum < numChannelsCached; ++channelNum)
{
ThumbData* channelData = chans.getUnchecked (channelNum);
MinMaxValue* cacheData = getData (channelNum, 0);
const double timeToThumbSampleFactor = rate / (double) sampsPerThumbSample;
startTime = cachedStart;
int sample = roundToInt (startTime * timeToThumbSampleFactor);
for (int i = numSamples; --i >= 0;)
{
const int nextSample = roundToInt ((startTime + timePerPixel) * timeToThumbSampleFactor);
channelData->getMinMax (sample, nextSample, *cacheData);
++cacheData;
startTime += timePerPixel;
sample = nextSample;
}
}
}
return true;
}
MinMaxValue* getData (const int channelNum, const int cacheIndex) noexcept
{
jassert (isPositiveAndBelow (channelNum, numChannelsCached) && isPositiveAndBelow (cacheIndex, data.size()));
return data.getRawDataPointer() + channelNum * numSamplesCached
+ cacheIndex;
}
void ensureSize (const int numSamples)
{
const int itemsRequired = numSamples * numChannelsCached;
if (data.size() < itemsRequired)
data.insertMultiple (-1, MinMaxValue(), itemsRequired - data.size());
}
};
//==============================================================================
AudioThumbnail::AudioThumbnail (const int originalSamplesPerThumbnailSample,
AudioFormatManager& formatManager,
AudioThumbnailCache& cacheToUse)
: formatManagerToUse (formatManager),
cache (cacheToUse),
window (new CachedWindow()),
samplesPerThumbSample (originalSamplesPerThumbnailSample),
totalSamples (0),
numSamplesFinished (0),
numChannels (0),
sampleRate (0)
{
}
AudioThumbnail::~AudioThumbnail()
{
clear();
}
void AudioThumbnail::clear()
{
source = nullptr;
const ScopedLock sl (lock);
clearChannelData();
}
void AudioThumbnail::clearChannelData()
{
window->invalidate();
channels.clear();
totalSamples = numSamplesFinished = 0;
numChannels = 0;
sampleRate = 0;
sendChangeMessage();
}
void AudioThumbnail::reset (int newNumChannels, double newSampleRate, int64 totalSamplesInSource)
{
clear();
const ScopedLock sl (lock);
numChannels = newNumChannels;
sampleRate = newSampleRate;
totalSamples = totalSamplesInSource;
createChannels (1 + (int) (totalSamplesInSource / samplesPerThumbSample));
}
void AudioThumbnail::createChannels (const int length)
{
while (channels.size() < numChannels)
channels.add (new ThumbData (length));
}
//==============================================================================
bool AudioThumbnail::loadFrom (InputStream& rawInput)
{
BufferedInputStream input (rawInput, 4096);
if (input.readByte() != 'j' || input.readByte() != 'a' || input.readByte() != 't' || input.readByte() != 'm')
return false;
const ScopedLock sl (lock);
clearChannelData();
samplesPerThumbSample = input.readInt();
totalSamples = input.readInt64(); // Total number of source samples.
numSamplesFinished = input.readInt64(); // Number of valid source samples that have been read into the thumbnail.
int32 numThumbnailSamples = input.readInt(); // Number of samples in the thumbnail data.
numChannels = input.readInt(); // Number of audio channels.
sampleRate = input.readInt(); // Source sample rate.
input.skipNextBytes (16); // (reserved)
createChannels (numThumbnailSamples);
for (int i = 0; i < numThumbnailSamples; ++i)
for (int chan = 0; chan < numChannels; ++chan)
channels.getUnchecked(chan)->getData(i)->read (input);
return true;
}
void AudioThumbnail::saveTo (OutputStream& output) const
{
const ScopedLock sl (lock);
const int numThumbnailSamples = channels.size() == 0 ? 0 : channels.getUnchecked(0)->getSize();
output.write ("jatm", 4);
output.writeInt (samplesPerThumbSample);
output.writeInt64 (totalSamples);
output.writeInt64 (numSamplesFinished);
output.writeInt (numThumbnailSamples);
output.writeInt (numChannels);
output.writeInt ((int) sampleRate);
output.writeInt64 (0);
output.writeInt64 (0);
for (int i = 0; i < numThumbnailSamples; ++i)
for (int chan = 0; chan < numChannels; ++chan)
channels.getUnchecked(chan)->getData(i)->write (output);
}
//==============================================================================
bool AudioThumbnail::setDataSource (LevelDataSource* newSource)
{
jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager());
numSamplesFinished = 0;
if (cache.loadThumb (*this, newSource->hashCode) && isFullyLoaded())
{
source = newSource; // (make sure this isn't done before loadThumb is called)
source->lengthInSamples = totalSamples;
source->sampleRate = sampleRate;
source->numChannels = (unsigned int) numChannels;
source->numSamplesFinished = numSamplesFinished;
}
else
{
source = newSource; // (make sure this isn't done before loadThumb is called)
const ScopedLock sl (lock);
source->initialise (numSamplesFinished);
totalSamples = source->lengthInSamples;
sampleRate = source->sampleRate;
numChannels = (int32) source->numChannels;
createChannels (1 + (int) (totalSamples / samplesPerThumbSample));
}
return sampleRate > 0 && totalSamples > 0;
}
bool AudioThumbnail::setSource (InputSource* const newSource)
{
clear();
return newSource != nullptr && setDataSource (new LevelDataSource (*this, newSource));
}
void AudioThumbnail::setReader (AudioFormatReader* newReader, int64 hash)
{
clear();
if (newReader != nullptr)
setDataSource (new LevelDataSource (*this, newReader, hash));
}
int64 AudioThumbnail::getHashCode() const
{
return source == nullptr ? 0 : source->hashCode;
}
void AudioThumbnail::addBlock (const int64 startSample, const AudioSampleBuffer& incoming,
int startOffsetInBuffer, int numSamples)
{
jassert (startSample >= 0);
const int firstThumbIndex = (int) (startSample / samplesPerThumbSample);
const int lastThumbIndex = (int) ((startSample + numSamples + (samplesPerThumbSample - 1)) / samplesPerThumbSample);
const int numToDo = lastThumbIndex - firstThumbIndex;
if (numToDo > 0)
{
const int numChans = jmin (channels.size(), incoming.getNumChannels());
const HeapBlock<MinMaxValue> thumbData ((size_t) (numToDo * numChans));
const HeapBlock<MinMaxValue*> thumbChannels ((size_t) numChans);
for (int chan = 0; chan < numChans; ++chan)
{
const float* const sourceData = incoming.getReadPointer (chan, startOffsetInBuffer);
MinMaxValue* const dest = thumbData + numToDo * chan;
thumbChannels [chan] = dest;
for (int i = 0; i < numToDo; ++i)
{
const int start = i * samplesPerThumbSample;
dest[i].setFloat (FloatVectorOperations::findMinAndMax (sourceData + start, jmin (samplesPerThumbSample, numSamples - start)));
}
}
setLevels (thumbChannels, firstThumbIndex, numChans, numToDo);
}
}
void AudioThumbnail::setLevels (const MinMaxValue* const* values, int thumbIndex, int numChans, int numValues)
{
const ScopedLock sl (lock);
for (int i = jmin (numChans, channels.size()); --i >= 0;)
channels.getUnchecked(i)->write (values[i], thumbIndex, numValues);
const int64 start = thumbIndex * (int64) samplesPerThumbSample;
const int64 end = (thumbIndex + numValues) * (int64) samplesPerThumbSample;
if (numSamplesFinished >= start && end > numSamplesFinished)
numSamplesFinished = end;
totalSamples = jmax (numSamplesFinished, totalSamples);
window->invalidate();
sendChangeMessage();
}
//==============================================================================
int AudioThumbnail::getNumChannels() const noexcept
{
return numChannels;
}
double AudioThumbnail::getTotalLength() const noexcept
{
return sampleRate > 0 ? (totalSamples / sampleRate) : 0;
}
bool AudioThumbnail::isFullyLoaded() const noexcept
{
return numSamplesFinished >= totalSamples - samplesPerThumbSample;
}
double AudioThumbnail::getProportionComplete() const noexcept
{
return jlimit (0.0, 1.0, numSamplesFinished / (double) jmax ((int64) 1, totalSamples));
}
int64 AudioThumbnail::getNumSamplesFinished() const noexcept
{
return numSamplesFinished;
}
float AudioThumbnail::getApproximatePeak() const
{
const ScopedLock sl (lock);
int peak = 0;
for (int i = channels.size(); --i >= 0;)
peak = jmax (peak, channels.getUnchecked(i)->getPeak());
return jlimit (0, 127, peak) / 127.0f;
}
void AudioThumbnail::getApproximateMinMax (const double startTime, const double endTime, const int channelIndex,
float& minValue, float& maxValue) const noexcept
{
const ScopedLock sl (lock);
MinMaxValue result;
const ThumbData* const data = channels [channelIndex];
if (data != nullptr && sampleRate > 0)
{
const int firstThumbIndex = (int) ((startTime * sampleRate) / samplesPerThumbSample);
const int lastThumbIndex = (int) (((endTime * sampleRate) + samplesPerThumbSample - 1) / samplesPerThumbSample);
data->getMinMax (jmax (0, firstThumbIndex), lastThumbIndex, result);
}
minValue = result.getMinValue() / 128.0f;
maxValue = result.getMaxValue() / 128.0f;
}
void AudioThumbnail::drawChannel (Graphics& g, const Rectangle<int>& area, double startTime,
double endTime, int channelNum, float verticalZoomFactor)
{
const ScopedLock sl (lock);
window->drawChannel (g, area, startTime, endTime, channelNum, verticalZoomFactor,
sampleRate, numChannels, samplesPerThumbSample, source, channels);
}
void AudioThumbnail::drawChannels (Graphics& g, const Rectangle<int>& area, double startTimeSeconds,
double endTimeSeconds, float verticalZoomFactor)
{
for (int i = 0; i < numChannels; ++i)
{
const int y1 = roundToInt ((i * area.getHeight()) / numChannels);
const int y2 = roundToInt (((i + 1) * area.getHeight()) / numChannels);
drawChannel (g, Rectangle<int> (area.getX(), area.getY() + y1, area.getWidth(), y2 - y1),
startTimeSeconds, endTimeSeconds, i, verticalZoomFactor);
}
}

View file

@ -0,0 +1,227 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE 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.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_AUDIOTHUMBNAIL_H_INCLUDED
#define JUCE_AUDIOTHUMBNAIL_H_INCLUDED
//==============================================================================
/**
Makes it easy to quickly draw scaled views of the waveform shape of an
audio file.
To use this class, just create an AudioThumbNail class for the file you want
to draw, call setSource to tell it which file or resource to use, then call
drawChannel() to draw it.
The class will asynchronously scan the wavefile to create its scaled-down view,
so you should make your UI repaint itself as this data comes in. To do this, the
AudioThumbnail is a ChangeBroadcaster, and will broadcast a message when its
listeners should repaint themselves.
The thumbnail stores an internal low-res version of the wave data, and this can
be loaded and saved to avoid having to scan the file again.
@see AudioThumbnailCache, AudioThumbnailBase
*/
class JUCE_API AudioThumbnail : public AudioThumbnailBase
{
public:
//==============================================================================
/** Creates an audio thumbnail.
@param sourceSamplesPerThumbnailSample when creating a stored, low-res version
of the audio data, this is the scale at which it should be done. (This
number is the number of original samples that will be averaged for each
low-res sample)
@param formatManagerToUse the audio format manager that is used to open the file
@param cacheToUse an instance of an AudioThumbnailCache - this provides a background
thread and storage that is used to by the thumbnail, and the cache
object can be shared between multiple thumbnails
*/
AudioThumbnail (int sourceSamplesPerThumbnailSample,
AudioFormatManager& formatManagerToUse,
AudioThumbnailCache& cacheToUse);
/** Destructor. */
~AudioThumbnail();
//==============================================================================
/** Clears and resets the thumbnail. */
void clear();
/** Specifies the file or stream that contains the audio file.
For a file, just call
@code
setSource (new FileInputSource (file))
@endcode
You can pass a zero in here to clear the thumbnail.
The source that is passed in will be deleted by this object when it is no longer needed.
@returns true if the source could be opened as a valid audio file, false if this failed for
some reason.
*/
bool setSource (InputSource* newSource);
/** Gives the thumbnail an AudioFormatReader to use directly.
This will start parsing the audio in a background thread (unless the hash code
can be looked-up successfully in the thumbnail cache). Note that the reader
object will be held by the thumbnail and deleted later when no longer needed.
The thumbnail will actually keep hold of this reader until you clear the thumbnail
or change the input source, so the file will be held open for all this time. If
you don't want the thumbnail to keep a file handle open continuously, you
should use the setSource() method instead, which will only open the file when
it needs to.
*/
void setReader (AudioFormatReader* newReader, int64 hashCode);
/** Resets the thumbnail, ready for adding data with the specified format.
If you're going to generate a thumbnail yourself, call this before using addBlock()
to add the data.
*/
void reset (int numChannels, double sampleRate, int64 totalSamplesInSource = 0);
/** Adds a block of level data to the thumbnail.
Call reset() before using this, to tell the thumbnail about the data format.
*/
void addBlock (int64 sampleNumberInSource, const AudioSampleBuffer& newData,
int startOffsetInBuffer, int numSamples);
//==============================================================================
/** Reloads the low res thumbnail data from an input stream.
This is not an audio file stream! It takes a stream of thumbnail data that would
previously have been created by the saveTo() method.
@see saveTo
*/
bool loadFrom (InputStream& input);
/** Saves the low res thumbnail data to an output stream.
The data that is written can later be reloaded using loadFrom().
@see loadFrom
*/
void saveTo (OutputStream& output) const;
//==============================================================================
/** Returns the number of channels in the file. */
int getNumChannels() const noexcept;
/** Returns the length of the audio file, in seconds. */
double getTotalLength() const noexcept;
/** Draws the waveform for a channel.
The waveform will be drawn within the specified rectangle, where startTime
and endTime specify the times within the audio file that should be positioned
at the left and right edges of the rectangle.
The waveform will be scaled vertically so that a full-volume sample will fill
the rectangle vertically, but you can also specify an extra vertical scale factor
with the verticalZoomFactor parameter.
*/
void drawChannel (Graphics& g,
const Rectangle<int>& area,
double startTimeSeconds,
double endTimeSeconds,
int channelNum,
float verticalZoomFactor);
/** Draws the waveforms for all channels in the thumbnail.
This will call drawChannel() to render each of the thumbnail's channels, stacked
above each other within the specified area.
@see drawChannel
*/
void drawChannels (Graphics& g,
const Rectangle<int>& area,
double startTimeSeconds,
double endTimeSeconds,
float verticalZoomFactor);
/** Returns true if the low res preview is fully generated. */
bool isFullyLoaded() const noexcept;
/** Returns a value between 0 and 1 to indicate the progress towards loading the entire file. */
double getProportionComplete() const noexcept;
/** Returns the number of samples that have been set in the thumbnail. */
int64 getNumSamplesFinished() const noexcept;
/** Returns the highest level in the thumbnail.
Note that because the thumb only stores low-resolution data, this isn't
an accurate representation of the highest value, it's only a rough approximation.
*/
float getApproximatePeak() const;
/** Reads the approximate min and max levels from a section of the thumbnail.
The lowest and highest samples are returned in minValue and maxValue, but obviously
because the thumb only stores low-resolution data, these numbers will only be a rough
approximation of the true values.
*/
void getApproximateMinMax (double startTime, double endTime, int channelIndex,
float& minValue, float& maxValue) const noexcept;
/** Returns the hash code that was set by setSource() or setReader(). */
int64 getHashCode() const;
private:
//==============================================================================
AudioFormatManager& formatManagerToUse;
AudioThumbnailCache& cache;
class LevelDataSource;
struct MinMaxValue;
class ThumbData;
class CachedWindow;
friend class LevelDataSource;
friend class ThumbData;
friend class CachedWindow;
friend struct ContainerDeletePolicy<LevelDataSource>;
friend struct ContainerDeletePolicy<ThumbData>;
friend struct ContainerDeletePolicy<CachedWindow>;
ScopedPointer<LevelDataSource> source;
ScopedPointer<CachedWindow> window;
OwnedArray<ThumbData> channels;
int32 samplesPerThumbSample;
int64 totalSamples, numSamplesFinished;
int32 numChannels;
double sampleRate;
CriticalSection lock;
void clearChannelData();
bool setDataSource (LevelDataSource* newSource);
void setLevels (const MinMaxValue* const* values, int thumbIndex, int numChans, int numValues);
void createChannels (int length);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioThumbnail)
};
#endif // JUCE_AUDIOTHUMBNAIL_H_INCLUDED

View file

@ -0,0 +1,155 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE 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.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_AUDIOTHUMBNAILBASE_H_INCLUDED
#define JUCE_AUDIOTHUMBNAILBASE_H_INCLUDED
class AudioThumbnailCache;
//==============================================================================
/**
Provides a base for classes that can store and draw scaled views of an
audio waveform.
Typically, you'll want to use the derived class AudioThumbnail, which provides
a concrete implementation.
@see AudioThumbnail, AudioThumbnailCache
*/
class JUCE_API AudioThumbnailBase : public ChangeBroadcaster,
public AudioFormatWriter::ThreadedWriter::IncomingDataReceiver
{
public:
//==============================================================================
AudioThumbnailBase() {}
virtual ~AudioThumbnailBase() {}
//==============================================================================
/** Clears and resets the thumbnail. */
virtual void clear() = 0;
/** Specifies the file or stream that contains the audio file.
For a file, just call
@code
setSource (new FileInputSource (file))
@endcode
You can pass a zero in here to clear the thumbnail.
The source that is passed in will be deleted by this object when it is no longer needed.
@returns true if the source could be opened as a valid audio file, false if this failed for
some reason.
*/
virtual bool setSource (InputSource* newSource) = 0;
/** Gives the thumbnail an AudioFormatReader to use directly.
This will start parsing the audio in a background thread (unless the hash code
can be looked-up successfully in the thumbnail cache). Note that the reader
object will be held by the thumbnail and deleted later when no longer needed.
The thumbnail will actually keep hold of this reader until you clear the thumbnail
or change the input source, so the file will be held open for all this time. If
you don't want the thumbnail to keep a file handle open continuously, you
should use the setSource() method instead, which will only open the file when
it needs to.
*/
virtual void setReader (AudioFormatReader* newReader, int64 hashCode) = 0;
//==============================================================================
/** Reloads the low res thumbnail data from an input stream.
This is not an audio file stream! It takes a stream of thumbnail data that would
previously have been created by the saveTo() method.
@see saveTo
*/
virtual bool loadFrom (InputStream& input) = 0;
/** Saves the low res thumbnail data to an output stream.
The data that is written can later be reloaded using loadFrom().
@see loadFrom
*/
virtual void saveTo (OutputStream& output) const = 0;
//==============================================================================
/** Returns the number of channels in the file. */
virtual int getNumChannels() const noexcept = 0;
/** Returns the length of the audio file, in seconds. */
virtual double getTotalLength() const noexcept = 0;
/** Draws the waveform for a channel.
The waveform will be drawn within the specified rectangle, where startTime
and endTime specify the times within the audio file that should be positioned
at the left and right edges of the rectangle.
The waveform will be scaled vertically so that a full-volume sample will fill
the rectangle vertically, but you can also specify an extra vertical scale factor
with the verticalZoomFactor parameter.
*/
virtual void drawChannel (Graphics& g,
const Rectangle<int>& area,
double startTimeSeconds,
double endTimeSeconds,
int channelNum,
float verticalZoomFactor) = 0;
/** Draws the waveforms for all channels in the thumbnail.
This will call drawChannel() to render each of the thumbnail's channels, stacked
above each other within the specified area.
@see drawChannel
*/
virtual void drawChannels (Graphics& g,
const Rectangle<int>& area,
double startTimeSeconds,
double endTimeSeconds,
float verticalZoomFactor) = 0;
/** Returns true if the low res preview is fully generated. */
virtual bool isFullyLoaded() const noexcept = 0;
/** Returns the number of samples that have been set in the thumbnail. */
virtual int64 getNumSamplesFinished() const noexcept = 0;
/** Returns the highest level in the thumbnail.
Note that because the thumb only stores low-resolution data, this isn't
an accurate representation of the highest value, it's only a rough approximation.
*/
virtual float getApproximatePeak() const = 0;
/** Reads the approximate min and max levels from a section of the thumbnail.
The lowest and highest samples are returned in minValue and maxValue, but obviously
because the thumb only stores low-resolution data, these numbers will only be a rough
approximation of the true values.
*/
virtual void getApproximateMinMax (double startTime, double endTime, int channelIndex,
float& minValue, float& maxValue) const noexcept = 0;
/** Returns the hash code that was set by setSource() or setReader(). */
virtual int64 getHashCode() const = 0;
};
#endif // JUCE_AUDIOTHUMBNAILBASE_H_INCLUDED

View file

@ -0,0 +1,191 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE 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.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
class AudioThumbnailCache::ThumbnailCacheEntry
{
public:
ThumbnailCacheEntry (const int64 hashCode)
: hash (hashCode),
lastUsed (Time::getMillisecondCounter())
{
}
ThumbnailCacheEntry (InputStream& in)
: hash (in.readInt64()),
lastUsed (0)
{
const int64 len = in.readInt64();
in.readIntoMemoryBlock (data, (ssize_t) len);
}
void write (OutputStream& out)
{
out.writeInt64 (hash);
out.writeInt64 ((int64) data.getSize());
out << data;
}
int64 hash;
uint32 lastUsed;
MemoryBlock data;
private:
JUCE_LEAK_DETECTOR (ThumbnailCacheEntry)
};
//==============================================================================
AudioThumbnailCache::AudioThumbnailCache (const int maxNumThumbs)
: thread ("thumb cache"),
maxNumThumbsToStore (maxNumThumbs)
{
jassert (maxNumThumbsToStore > 0);
thread.startThread (2);
}
AudioThumbnailCache::~AudioThumbnailCache()
{
}
AudioThumbnailCache::ThumbnailCacheEntry* AudioThumbnailCache::findThumbFor (const int64 hash) const
{
for (int i = thumbs.size(); --i >= 0;)
if (thumbs.getUnchecked(i)->hash == hash)
return thumbs.getUnchecked(i);
return nullptr;
}
int AudioThumbnailCache::findOldestThumb() const
{
int oldest = 0;
uint32 oldestTime = Time::getMillisecondCounter() + 1;
for (int i = thumbs.size(); --i >= 0;)
{
const ThumbnailCacheEntry* const te = thumbs.getUnchecked(i);
if (te->lastUsed < oldestTime)
{
oldest = i;
oldestTime = te->lastUsed;
}
}
return oldest;
}
bool AudioThumbnailCache::loadThumb (AudioThumbnailBase& thumb, const int64 hashCode)
{
const ScopedLock sl (lock);
if (ThumbnailCacheEntry* te = findThumbFor (hashCode))
{
te->lastUsed = Time::getMillisecondCounter();
MemoryInputStream in (te->data, false);
thumb.loadFrom (in);
return true;
}
return loadNewThumb (thumb, hashCode);
}
void AudioThumbnailCache::storeThumb (const AudioThumbnailBase& thumb,
const int64 hashCode)
{
const ScopedLock sl (lock);
ThumbnailCacheEntry* te = findThumbFor (hashCode);
if (te == nullptr)
{
te = new ThumbnailCacheEntry (hashCode);
if (thumbs.size() < maxNumThumbsToStore)
thumbs.add (te);
else
thumbs.set (findOldestThumb(), te);
}
{
MemoryOutputStream out (te->data, false);
thumb.saveTo (out);
}
saveNewlyFinishedThumbnail (thumb, hashCode);
}
void AudioThumbnailCache::clear()
{
const ScopedLock sl (lock);
thumbs.clear();
}
void AudioThumbnailCache::removeThumb (const int64 hashCode)
{
const ScopedLock sl (lock);
for (int i = thumbs.size(); --i >= 0;)
if (thumbs.getUnchecked(i)->hash == hashCode)
thumbs.remove (i);
}
static inline int getThumbnailCacheFileMagicHeader() noexcept
{
return (int) ByteOrder::littleEndianInt ("ThmC");
}
bool AudioThumbnailCache::readFromStream (InputStream& source)
{
if (source.readInt() != getThumbnailCacheFileMagicHeader())
return false;
const ScopedLock sl (lock);
clear();
int numThumbnails = jmin (maxNumThumbsToStore, source.readInt());
while (--numThumbnails >= 0 && ! source.isExhausted())
thumbs.add (new ThumbnailCacheEntry (source));
return true;
}
void AudioThumbnailCache::writeToStream (OutputStream& out)
{
const ScopedLock sl (lock);
out.writeInt (getThumbnailCacheFileMagicHeader());
out.writeInt (thumbs.size());
for (int i = 0; i < thumbs.size(); ++i)
thumbs.getUnchecked(i)->write (out);
}
void AudioThumbnailCache::saveNewlyFinishedThumbnail (const AudioThumbnailBase&, int64)
{
}
bool AudioThumbnailCache::loadNewThumb (AudioThumbnailBase&, int64)
{
return false;
}

View file

@ -0,0 +1,118 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE 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.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_AUDIOTHUMBNAILCACHE_H_INCLUDED
#define JUCE_AUDIOTHUMBNAILCACHE_H_INCLUDED
//==============================================================================
/**
An instance of this class is used to manage multiple AudioThumbnail objects.
The cache runs a single background thread that is shared by all the thumbnails
that need it, and it maintains a set of low-res previews in memory, to avoid
having to re-scan audio files too often.
@see AudioThumbnail
*/
class JUCE_API AudioThumbnailCache
{
public:
//==============================================================================
/** Creates a cache object.
The maxNumThumbsToStore parameter lets you specify how many previews should
be kept in memory at once.
*/
explicit AudioThumbnailCache (int maxNumThumbsToStore);
/** Destructor. */
virtual ~AudioThumbnailCache();
//==============================================================================
/** Clears out any stored thumbnails. */
void clear();
/** Reloads the specified thumb if this cache contains the appropriate stored
data.
This is called automatically by the AudioThumbnail class, so you shouldn't
normally need to call it directly.
*/
bool loadThumb (AudioThumbnailBase& thumb, int64 hashCode);
/** Stores the cachable data from the specified thumb in this cache.
This is called automatically by the AudioThumbnail class, so you shouldn't
normally need to call it directly.
*/
void storeThumb (const AudioThumbnailBase& thumb, int64 hashCode);
/** Tells the cache to forget about the thumb with the given hashcode. */
void removeThumb (int64 hashCode);
//==============================================================================
/** Attempts to re-load a saved cache of thumbnails from a stream.
The cache data must have been written by the writeToStream() method.
This will replace all currently-loaded thumbnails with the new data.
*/
bool readFromStream (InputStream& source);
/** Writes all currently-loaded cache data to a stream.
The resulting data can be re-loaded with readFromStream().
*/
void writeToStream (OutputStream& stream);
/** Returns the thread that client thumbnails can use. */
TimeSliceThread& getTimeSliceThread() noexcept { return thread; }
protected:
/** This can be overridden to provide a custom callback for saving thumbnails
once they have finished being loaded.
*/
virtual void saveNewlyFinishedThumbnail (const AudioThumbnailBase&, int64 hashCode);
/** This can be overridden to provide a custom callback for loading thumbnails
from pre-saved files to save the cache the trouble of having to create them.
*/
virtual bool loadNewThumb (AudioThumbnailBase&, int64 hashCode);
private:
//==============================================================================
TimeSliceThread thread;
class ThumbnailCacheEntry;
friend struct ContainerDeletePolicy<ThumbnailCacheEntry>;
OwnedArray<ThumbnailCacheEntry> thumbs;
CriticalSection lock;
int maxNumThumbsToStore;
ThumbnailCacheEntry* findThumbFor (int64 hash) const;
int findOldestThumb() const;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioThumbnailCache)
};
#endif // JUCE_AUDIOTHUMBNAILCACHE_H_INCLUDED

View file

@ -0,0 +1,893 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE 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.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
class MidiKeyboardUpDownButton : public Button
{
public:
MidiKeyboardUpDownButton (MidiKeyboardComponent& comp, const int d)
: Button (String::empty),
owner (comp),
delta (d)
{
setOpaque (true);
}
void clicked() override
{
int note = owner.getLowestVisibleKey();
if (delta < 0)
note = (note - 1) / 12;
else
note = note / 12 + 1;
owner.setLowestVisibleKey (note * 12);
}
void paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown) override
{
owner.drawUpDownButton (g, getWidth(), getHeight(),
isMouseOverButton, isButtonDown,
delta > 0);
}
private:
MidiKeyboardComponent& owner;
const int delta;
JUCE_DECLARE_NON_COPYABLE (MidiKeyboardUpDownButton)
};
//==============================================================================
MidiKeyboardComponent::MidiKeyboardComponent (MidiKeyboardState& s,
const Orientation o)
: state (s),
xOffset (0),
blackNoteLength (1),
keyWidth (16.0f),
orientation (o),
midiChannel (1),
midiInChannelMask (0xffff),
velocity (1.0f),
shouldCheckState (false),
rangeStart (0),
rangeEnd (127),
firstKey (12 * 4.0f),
canScroll (true),
useMousePositionForVelocity (true),
shouldCheckMousePos (false),
keyMappingOctave (6),
octaveNumForMiddleC (3)
{
addChildComponent (scrollDown = new MidiKeyboardUpDownButton (*this, -1));
addChildComponent (scrollUp = new MidiKeyboardUpDownButton (*this, 1));
// initialise with a default set of querty key-mappings..
const char* const keymap = "awsedftgyhujkolp;";
for (int i = 0; keymap[i] != 0; ++i)
setKeyPressForNote (KeyPress (keymap[i], 0, 0), i);
mouseOverNotes.insertMultiple (0, -1, 32);
mouseDownNotes.insertMultiple (0, -1, 32);
setOpaque (true);
setWantsKeyboardFocus (true);
state.addListener (this);
startTimer (1000 / 20);
}
MidiKeyboardComponent::~MidiKeyboardComponent()
{
state.removeListener (this);
}
//==============================================================================
void MidiKeyboardComponent::setKeyWidth (const float widthInPixels)
{
keyWidth = widthInPixels;
resized();
}
void MidiKeyboardComponent::setOrientation (const Orientation newOrientation)
{
if (orientation != newOrientation)
{
orientation = newOrientation;
resized();
}
}
void MidiKeyboardComponent::setAvailableRange (const int lowestNote,
const int highestNote)
{
jassert (lowestNote >= 0 && lowestNote <= 127);
jassert (highestNote >= 0 && highestNote <= 127);
jassert (lowestNote <= highestNote);
if (rangeStart != lowestNote || rangeEnd != highestNote)
{
rangeStart = jlimit (0, 127, lowestNote);
rangeEnd = jlimit (0, 127, highestNote);
firstKey = jlimit ((float) rangeStart, (float) rangeEnd, firstKey);
resized();
}
}
void MidiKeyboardComponent::setLowestVisibleKey (int noteNumber)
{
setLowestVisibleKeyFloat ((float) noteNumber);
}
void MidiKeyboardComponent::setLowestVisibleKeyFloat (float noteNumber)
{
noteNumber = jlimit ((float) rangeStart, (float) rangeEnd, noteNumber);
if (noteNumber != firstKey)
{
const bool hasMoved = (((int) firstKey) != (int) noteNumber);
firstKey = noteNumber;
if (hasMoved)
sendChangeMessage();
resized();
}
}
void MidiKeyboardComponent::setScrollButtonsVisible (const bool newCanScroll)
{
if (canScroll != newCanScroll)
{
canScroll = newCanScroll;
resized();
}
}
void MidiKeyboardComponent::colourChanged()
{
repaint();
}
//==============================================================================
void MidiKeyboardComponent::setMidiChannel (const int midiChannelNumber)
{
jassert (midiChannelNumber > 0 && midiChannelNumber <= 16);
if (midiChannel != midiChannelNumber)
{
resetAnyKeysInUse();
midiChannel = jlimit (1, 16, midiChannelNumber);
}
}
void MidiKeyboardComponent::setMidiChannelsToDisplay (const int midiChannelMask)
{
midiInChannelMask = midiChannelMask;
shouldCheckState = true;
}
void MidiKeyboardComponent::setVelocity (const float v, const bool useMousePosition)
{
velocity = jlimit (0.0f, 1.0f, v);
useMousePositionForVelocity = useMousePosition;
}
//==============================================================================
void MidiKeyboardComponent::getKeyPosition (int midiNoteNumber, const float keyWidth_, int& x, int& w) const
{
jassert (midiNoteNumber >= 0 && midiNoteNumber < 128);
static const float blackNoteWidth = 0.7f;
static const float notePos[] = { 0.0f, 1 - blackNoteWidth * 0.6f,
1.0f, 2 - blackNoteWidth * 0.4f,
2.0f,
3.0f, 4 - blackNoteWidth * 0.7f,
4.0f, 5 - blackNoteWidth * 0.5f,
5.0f, 6 - blackNoteWidth * 0.3f,
6.0f };
const int octave = midiNoteNumber / 12;
const int note = midiNoteNumber % 12;
x = roundToInt (octave * 7.0f * keyWidth_ + notePos [note] * keyWidth_);
w = roundToInt (MidiMessage::isMidiNoteBlack (note) ? blackNoteWidth * keyWidth_ : keyWidth_);
}
void MidiKeyboardComponent::getKeyPos (int midiNoteNumber, int& x, int& w) const
{
getKeyPosition (midiNoteNumber, keyWidth, x, w);
int rx, rw;
getKeyPosition (rangeStart, keyWidth, rx, rw);
x -= xOffset + rx;
}
Rectangle<int> MidiKeyboardComponent::getWhiteNotePos (int noteNum) const
{
int x, w;
getKeyPos (noteNum, x, w);
Rectangle<int> pos;
switch (orientation)
{
case horizontalKeyboard: pos.setBounds (x, 0, w, getHeight()); break;
case verticalKeyboardFacingLeft: pos.setBounds (0, x, getWidth(), w); break;
case verticalKeyboardFacingRight: pos.setBounds (0, getHeight() - x - w, getWidth(), w); break;
default: break;
}
return pos;
}
int MidiKeyboardComponent::getKeyStartPosition (const int midiNoteNumber) const
{
int x, w;
getKeyPos (midiNoteNumber, x, w);
return x;
}
int MidiKeyboardComponent::getNoteAtPosition (Point<int> p)
{
float v;
return xyToNote (p, v);
}
const uint8 MidiKeyboardComponent::whiteNotes[] = { 0, 2, 4, 5, 7, 9, 11 };
const uint8 MidiKeyboardComponent::blackNotes[] = { 1, 3, 6, 8, 10 };
int MidiKeyboardComponent::xyToNote (Point<int> pos, float& mousePositionVelocity)
{
if (! reallyContains (pos, false))
return -1;
Point<int> p (pos);
if (orientation != horizontalKeyboard)
{
p = Point<int> (p.y, p.x);
if (orientation == verticalKeyboardFacingLeft)
p = Point<int> (p.x, getWidth() - p.y);
else
p = Point<int> (getHeight() - p.x, p.y);
}
return remappedXYToNote (p + Point<int> (xOffset, 0), mousePositionVelocity);
}
int MidiKeyboardComponent::remappedXYToNote (Point<int> pos, float& mousePositionVelocity) const
{
if (pos.getY() < blackNoteLength)
{
for (int octaveStart = 12 * (rangeStart / 12); octaveStart <= rangeEnd; octaveStart += 12)
{
for (int i = 0; i < 5; ++i)
{
const int note = octaveStart + blackNotes [i];
if (note >= rangeStart && note <= rangeEnd)
{
int kx, kw;
getKeyPos (note, kx, kw);
kx += xOffset;
if (pos.x >= kx && pos.x < kx + kw)
{
mousePositionVelocity = pos.y / (float) blackNoteLength;
return note;
}
}
}
}
}
for (int octaveStart = 12 * (rangeStart / 12); octaveStart <= rangeEnd; octaveStart += 12)
{
for (int i = 0; i < 7; ++i)
{
const int note = octaveStart + whiteNotes [i];
if (note >= rangeStart && note <= rangeEnd)
{
int kx, kw;
getKeyPos (note, kx, kw);
kx += xOffset;
if (pos.x >= kx && pos.x < kx + kw)
{
const int whiteNoteLength = (orientation == horizontalKeyboard) ? getHeight() : getWidth();
mousePositionVelocity = pos.y / (float) whiteNoteLength;
return note;
}
}
}
}
mousePositionVelocity = 0;
return -1;
}
//==============================================================================
void MidiKeyboardComponent::repaintNote (const int noteNum)
{
if (noteNum >= rangeStart && noteNum <= rangeEnd)
repaint (getWhiteNotePos (noteNum));
}
void MidiKeyboardComponent::paint (Graphics& g)
{
g.fillAll (Colours::white.overlaidWith (findColour (whiteNoteColourId)));
const Colour lineColour (findColour (keySeparatorLineColourId));
const Colour textColour (findColour (textLabelColourId));
int octave;
for (octave = 0; octave < 128; octave += 12)
{
for (int white = 0; white < 7; ++white)
{
const int noteNum = octave + whiteNotes [white];
if (noteNum >= rangeStart && noteNum <= rangeEnd)
{
const Rectangle<int> pos (getWhiteNotePos (noteNum));
drawWhiteNote (noteNum, g, pos.getX(), pos.getY(), pos.getWidth(), pos.getHeight(),
state.isNoteOnForChannels (midiInChannelMask, noteNum),
mouseOverNotes.contains (noteNum), lineColour, textColour);
}
}
}
float x1 = 0.0f, y1 = 0.0f, x2 = 0.0f, y2 = 0.0f;
const int width = getWidth();
const int height = getHeight();
if (orientation == verticalKeyboardFacingLeft)
{
x1 = width - 1.0f;
x2 = width - 5.0f;
}
else if (orientation == verticalKeyboardFacingRight)
x2 = 5.0f;
else
y2 = 5.0f;
int x, w;
getKeyPos (rangeEnd, x, w);
x += w;
const Colour shadowCol (findColour (shadowColourId));
g.setGradientFill (ColourGradient (shadowCol, x1, y1, shadowCol.withAlpha (0.0f), x2, y2, false));
switch (orientation)
{
case horizontalKeyboard: g.fillRect (0, 0, x, 5); break;
case verticalKeyboardFacingLeft: g.fillRect (width - 5, 0, 5, x); break;
case verticalKeyboardFacingRight: g.fillRect (0, 0, 5, x); break;
default: break;
}
g.setColour (lineColour);
switch (orientation)
{
case horizontalKeyboard: g.fillRect (0, height - 1, x, 1); break;
case verticalKeyboardFacingLeft: g.fillRect (0, 0, 1, x); break;
case verticalKeyboardFacingRight: g.fillRect (width - 1, 0, 1, x); break;
default: break;
}
const Colour blackNoteColour (findColour (blackNoteColourId));
for (octave = 0; octave < 128; octave += 12)
{
for (int black = 0; black < 5; ++black)
{
const int noteNum = octave + blackNotes [black];
if (noteNum >= rangeStart && noteNum <= rangeEnd)
{
getKeyPos (noteNum, x, w);
Rectangle<int> pos;
switch (orientation)
{
case horizontalKeyboard: pos.setBounds (x, 0, w, blackNoteLength); break;
case verticalKeyboardFacingLeft: pos.setBounds (width - blackNoteLength, x, blackNoteLength, w); break;
case verticalKeyboardFacingRight: pos.setBounds (0, height - x - w, blackNoteLength, w); break;
default: break;
}
drawBlackNote (noteNum, g, pos.getX(), pos.getY(), pos.getWidth(), pos.getHeight(),
state.isNoteOnForChannels (midiInChannelMask, noteNum),
mouseOverNotes.contains (noteNum), blackNoteColour);
}
}
}
}
void MidiKeyboardComponent::drawWhiteNote (int midiNoteNumber,
Graphics& g, int x, int y, int w, int h,
bool isDown, bool isOver,
const Colour& lineColour,
const Colour& textColour)
{
Colour c (Colours::transparentWhite);
if (isDown) c = findColour (keyDownOverlayColourId);
if (isOver) c = c.overlaidWith (findColour (mouseOverKeyOverlayColourId));
g.setColour (c);
g.fillRect (x, y, w, h);
const String text (getWhiteNoteText (midiNoteNumber));
if (text.isNotEmpty())
{
g.setColour (textColour);
g.setFont (Font (jmin (12.0f, keyWidth * 0.9f)).withHorizontalScale (0.8f));
switch (orientation)
{
case horizontalKeyboard: g.drawFittedText (text, x + 1, y, w - 1, h - 2, Justification::centredBottom, 1); break;
case verticalKeyboardFacingLeft: g.drawFittedText (text, x + 2, y + 2, w - 4, h - 4, Justification::centredLeft, 1); break;
case verticalKeyboardFacingRight: g.drawFittedText (text, x + 2, y + 2, w - 4, h - 4, Justification::centredRight, 1); break;
default: break;
}
}
g.setColour (lineColour);
switch (orientation)
{
case horizontalKeyboard: g.fillRect (x, y, 1, h); break;
case verticalKeyboardFacingLeft: g.fillRect (x, y, w, 1); break;
case verticalKeyboardFacingRight: g.fillRect (x, y + h - 1, w, 1); break;
default: break;
}
if (midiNoteNumber == rangeEnd)
{
switch (orientation)
{
case horizontalKeyboard: g.fillRect (x + w, y, 1, h); break;
case verticalKeyboardFacingLeft: g.fillRect (x, y + h, w, 1); break;
case verticalKeyboardFacingRight: g.fillRect (x, y - 1, w, 1); break;
default: break;
}
}
}
void MidiKeyboardComponent::drawBlackNote (int /*midiNoteNumber*/,
Graphics& g, int x, int y, int w, int h,
bool isDown, bool isOver,
const Colour& noteFillColour)
{
Colour c (noteFillColour);
if (isDown) c = c.overlaidWith (findColour (keyDownOverlayColourId));
if (isOver) c = c.overlaidWith (findColour (mouseOverKeyOverlayColourId));
g.setColour (c);
g.fillRect (x, y, w, h);
if (isDown)
{
g.setColour (noteFillColour);
g.drawRect (x, y, w, h);
}
else
{
g.setColour (c.brighter());
const int xIndent = jmax (1, jmin (w, h) / 8);
switch (orientation)
{
case horizontalKeyboard: g.fillRect (x + xIndent, y, w - xIndent * 2, 7 * h / 8); break;
case verticalKeyboardFacingLeft: g.fillRect (x + w / 8, y + xIndent, w - w / 8, h - xIndent * 2); break;
case verticalKeyboardFacingRight: g.fillRect (x, y + xIndent, 7 * w / 8, h - xIndent * 2); break;
default: break;
}
}
}
void MidiKeyboardComponent::setOctaveForMiddleC (const int octaveNum)
{
octaveNumForMiddleC = octaveNum;
repaint();
}
String MidiKeyboardComponent::getWhiteNoteText (const int midiNoteNumber)
{
if (keyWidth > 11.0f && midiNoteNumber % 12 == 0)
return MidiMessage::getMidiNoteName (midiNoteNumber, true, true, octaveNumForMiddleC);
return String::empty;
}
void MidiKeyboardComponent::drawUpDownButton (Graphics& g, int w, int h,
const bool mouseOver,
const bool buttonDown,
const bool movesOctavesUp)
{
g.fillAll (findColour (upDownButtonBackgroundColourId));
float angle;
switch (orientation)
{
case horizontalKeyboard: angle = movesOctavesUp ? 0.0f : 0.5f; break;
case verticalKeyboardFacingLeft: angle = movesOctavesUp ? 0.25f : 0.75f; break;
case verticalKeyboardFacingRight: angle = movesOctavesUp ? 0.75f : 0.25f; break;
default: jassertfalse; angle = 0; break;
}
Path path;
path.addTriangle (0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.5f);
path.applyTransform (AffineTransform::rotation (float_Pi * 2.0f * angle, 0.5f, 0.5f));
g.setColour (findColour (upDownButtonArrowColourId)
.withAlpha (buttonDown ? 1.0f : (mouseOver ? 0.6f : 0.4f)));
g.fillPath (path, path.getTransformToScaleToFit (1.0f, 1.0f, w - 2.0f, h - 2.0f, true));
}
void MidiKeyboardComponent::resized()
{
int w = getWidth();
int h = getHeight();
if (w > 0 && h > 0)
{
if (orientation != horizontalKeyboard)
std::swap (w, h);
blackNoteLength = roundToInt (h * 0.7f);
int kx2, kw2;
getKeyPos (rangeEnd, kx2, kw2);
kx2 += kw2;
if ((int) firstKey != rangeStart)
{
int kx1, kw1;
getKeyPos (rangeStart, kx1, kw1);
if (kx2 - kx1 <= w)
{
firstKey = (float) rangeStart;
sendChangeMessage();
repaint();
}
}
scrollDown->setVisible (canScroll && firstKey > (float) rangeStart);
xOffset = 0;
if (canScroll)
{
const int scrollButtonW = jmin (12, w / 2);
Rectangle<int> r (getLocalBounds());
if (orientation == horizontalKeyboard)
{
scrollDown->setBounds (r.removeFromLeft (scrollButtonW));
scrollUp ->setBounds (r.removeFromRight (scrollButtonW));
}
else if (orientation == verticalKeyboardFacingLeft)
{
scrollDown->setBounds (r.removeFromTop (scrollButtonW));
scrollUp ->setBounds (r.removeFromBottom (scrollButtonW));
}
else
{
scrollDown->setBounds (r.removeFromBottom (scrollButtonW));
scrollUp ->setBounds (r.removeFromTop (scrollButtonW));
}
int endOfLastKey, kw;
getKeyPos (rangeEnd, endOfLastKey, kw);
endOfLastKey += kw;
float mousePositionVelocity;
const int spaceAvailable = w;
const int lastStartKey = remappedXYToNote (Point<int> (endOfLastKey - spaceAvailable, 0), mousePositionVelocity) + 1;
if (lastStartKey >= 0 && ((int) firstKey) > lastStartKey)
{
firstKey = (float) jlimit (rangeStart, rangeEnd, lastStartKey);
sendChangeMessage();
}
int newOffset = 0;
getKeyPos ((int) firstKey, newOffset, kw);
xOffset = newOffset;
}
else
{
firstKey = (float) rangeStart;
}
getKeyPos (rangeEnd, kx2, kw2);
scrollUp->setVisible (canScroll && kx2 > w);
repaint();
}
}
//==============================================================================
void MidiKeyboardComponent::handleNoteOn (MidiKeyboardState*, int /*midiChannel*/, int /*midiNoteNumber*/, float /*velocity*/)
{
shouldCheckState = true; // (probably being called from the audio thread, so avoid blocking in here)
}
void MidiKeyboardComponent::handleNoteOff (MidiKeyboardState*, int /*midiChannel*/, int /*midiNoteNumber*/)
{
shouldCheckState = true; // (probably being called from the audio thread, so avoid blocking in here)
}
//==============================================================================
void MidiKeyboardComponent::resetAnyKeysInUse()
{
if (! keysPressed.isZero())
{
for (int i = 128; --i >= 0;)
if (keysPressed[i])
state.noteOff (midiChannel, i);
keysPressed.clear();
}
for (int i = mouseDownNotes.size(); --i >= 0;)
{
const int noteDown = mouseDownNotes.getUnchecked(i);
if (noteDown >= 0)
{
state.noteOff (midiChannel, noteDown);
mouseDownNotes.set (i, -1);
}
mouseOverNotes.set (i, -1);
}
}
void MidiKeyboardComponent::updateNoteUnderMouse (const MouseEvent& e, bool isDown)
{
updateNoteUnderMouse (e.getPosition(), isDown, e.source.getIndex());
}
void MidiKeyboardComponent::updateNoteUnderMouse (Point<int> pos, bool isDown, int fingerNum)
{
float mousePositionVelocity = 0.0f;
const int newNote = xyToNote (pos, mousePositionVelocity);
const int oldNote = mouseOverNotes.getUnchecked (fingerNum);
if (oldNote != newNote)
{
repaintNote (oldNote);
repaintNote (newNote);
mouseOverNotes.set (fingerNum, newNote);
}
int oldNoteDown = mouseDownNotes.getUnchecked (fingerNum);
if (isDown)
{
if (newNote != oldNoteDown)
{
if (oldNoteDown >= 0)
{
mouseDownNotes.set (fingerNum, -1);
if (! mouseDownNotes.contains (oldNoteDown))
state.noteOff (midiChannel, oldNoteDown);
}
if (newNote >= 0)
{
if (! useMousePositionForVelocity)
mousePositionVelocity = 1.0f;
state.noteOn (midiChannel, newNote, mousePositionVelocity * velocity);
mouseDownNotes.set (fingerNum, newNote);
}
}
}
else if (oldNoteDown >= 0)
{
mouseDownNotes.set (fingerNum, -1);
if (! mouseDownNotes.contains (oldNoteDown))
state.noteOff (midiChannel, oldNoteDown);
}
}
void MidiKeyboardComponent::mouseMove (const MouseEvent& e)
{
updateNoteUnderMouse (e, false);
shouldCheckMousePos = false;
}
void MidiKeyboardComponent::mouseDrag (const MouseEvent& e)
{
float mousePositionVelocity;
const int newNote = xyToNote (e.getPosition(), mousePositionVelocity);
if (newNote >= 0)
mouseDraggedToKey (newNote, e);
updateNoteUnderMouse (e, true);
}
bool MidiKeyboardComponent::mouseDownOnKey (int, const MouseEvent&) { return true; }
void MidiKeyboardComponent::mouseDraggedToKey (int, const MouseEvent&) {}
void MidiKeyboardComponent::mouseUpOnKey (int, const MouseEvent&) {}
void MidiKeyboardComponent::mouseDown (const MouseEvent& e)
{
float mousePositionVelocity;
const int newNote = xyToNote (e.getPosition(), mousePositionVelocity);
if (newNote >= 0 && mouseDownOnKey (newNote, e))
{
updateNoteUnderMouse (e, true);
shouldCheckMousePos = true;
}
}
void MidiKeyboardComponent::mouseUp (const MouseEvent& e)
{
updateNoteUnderMouse (e, false);
shouldCheckMousePos = false;
float mousePositionVelocity;
const int note = xyToNote (e.getPosition(), mousePositionVelocity);
if (note >= 0)
mouseUpOnKey (note, e);
}
void MidiKeyboardComponent::mouseEnter (const MouseEvent& e)
{
updateNoteUnderMouse (e, false);
}
void MidiKeyboardComponent::mouseExit (const MouseEvent& e)
{
updateNoteUnderMouse (e, false);
}
void MidiKeyboardComponent::mouseWheelMove (const MouseEvent&, const MouseWheelDetails& wheel)
{
const float amount = (orientation == horizontalKeyboard && wheel.deltaX != 0)
? wheel.deltaX : (orientation == verticalKeyboardFacingLeft ? wheel.deltaY
: -wheel.deltaY);
setLowestVisibleKeyFloat (firstKey - amount * keyWidth);
}
void MidiKeyboardComponent::timerCallback()
{
if (shouldCheckState)
{
shouldCheckState = false;
for (int i = rangeStart; i <= rangeEnd; ++i)
{
if (keysCurrentlyDrawnDown[i] != state.isNoteOnForChannels (midiInChannelMask, i))
{
keysCurrentlyDrawnDown.setBit (i, state.isNoteOnForChannels (midiInChannelMask, i));
repaintNote (i);
}
}
}
if (shouldCheckMousePos)
{
const Array<MouseInputSource>& mouseSources = Desktop::getInstance().getMouseSources();
for (MouseInputSource* mi = mouseSources.begin(), * const e = mouseSources.end(); mi != e; ++mi)
updateNoteUnderMouse (getLocalPoint (nullptr, mi->getScreenPosition()).roundToInt(), mi->isDragging(), mi->getIndex());
}
}
//==============================================================================
void MidiKeyboardComponent::clearKeyMappings()
{
resetAnyKeysInUse();
keyPressNotes.clear();
keyPresses.clear();
}
void MidiKeyboardComponent::setKeyPressForNote (const KeyPress& key,
const int midiNoteOffsetFromC)
{
removeKeyPressForNote (midiNoteOffsetFromC);
keyPressNotes.add (midiNoteOffsetFromC);
keyPresses.add (key);
}
void MidiKeyboardComponent::removeKeyPressForNote (const int midiNoteOffsetFromC)
{
for (int i = keyPressNotes.size(); --i >= 0;)
{
if (keyPressNotes.getUnchecked (i) == midiNoteOffsetFromC)
{
keyPressNotes.remove (i);
keyPresses.remove (i);
}
}
}
void MidiKeyboardComponent::setKeyPressBaseOctave (const int newOctaveNumber)
{
jassert (newOctaveNumber >= 0 && newOctaveNumber <= 10);
keyMappingOctave = newOctaveNumber;
}
bool MidiKeyboardComponent::keyStateChanged (const bool /*isKeyDown*/)
{
bool keyPressUsed = false;
for (int i = keyPresses.size(); --i >= 0;)
{
const int note = 12 * keyMappingOctave + keyPressNotes.getUnchecked (i);
if (keyPresses.getReference(i).isCurrentlyDown())
{
if (! keysPressed [note])
{
keysPressed.setBit (note);
state.noteOn (midiChannel, note, velocity);
keyPressUsed = true;
}
}
else
{
if (keysPressed [note])
{
keysPressed.clearBit (note);
state.noteOff (midiChannel, note);
keyPressUsed = true;
}
}
}
return keyPressUsed;
}
void MidiKeyboardComponent::focusLost (FocusChangeType)
{
resetAnyKeysInUse();
}

View file

@ -0,0 +1,409 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE 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.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_MIDIKEYBOARDCOMPONENT_H_INCLUDED
#define JUCE_MIDIKEYBOARDCOMPONENT_H_INCLUDED
//==============================================================================
/**
A component that displays a piano keyboard, whose notes can be clicked on.
This component will mimic a physical midi keyboard, showing the current state of
a MidiKeyboardState object. When the on-screen keys are clicked on, it will play these
notes by calling the noteOn() and noteOff() methods of its MidiKeyboardState object.
Another feature is that the computer keyboard can also be used to play notes. By
default it maps the top two rows of a standard querty keyboard to the notes, but
these can be remapped if needed. It will only respond to keypresses when it has
the keyboard focus, so to disable this feature you can call setWantsKeyboardFocus (false).
The component is also a ChangeBroadcaster, so if you want to be informed when the
keyboard is scrolled, you can register a ChangeListener for callbacks.
@see MidiKeyboardState
*/
class JUCE_API MidiKeyboardComponent : public Component,
public MidiKeyboardStateListener,
public ChangeBroadcaster,
private Timer
{
public:
//==============================================================================
/** The direction of the keyboard.
@see setOrientation
*/
enum Orientation
{
horizontalKeyboard,
verticalKeyboardFacingLeft,
verticalKeyboardFacingRight,
};
/** Creates a MidiKeyboardComponent.
@param state the midi keyboard model that this component will represent
@param orientation whether the keyboard is horizonal or vertical
*/
MidiKeyboardComponent (MidiKeyboardState& state,
Orientation orientation);
/** Destructor. */
~MidiKeyboardComponent();
//==============================================================================
/** Changes the velocity used in midi note-on messages that are triggered by clicking
on the component.
Values are 0 to 1.0, where 1.0 is the heaviest.
@see setMidiChannel
*/
void setVelocity (float velocity, bool useMousePositionForVelocity);
/** Changes the midi channel number that will be used for events triggered by clicking
on the component.
The channel must be between 1 and 16 (inclusive). This is the channel that will be
passed on to the MidiKeyboardState::noteOn() method when the user clicks the component.
Although this is the channel used for outgoing events, the component can display
incoming events from more than one channel - see setMidiChannelsToDisplay()
@see setVelocity
*/
void setMidiChannel (int midiChannelNumber);
/** Returns the midi channel that the keyboard is using for midi messages.
@see setMidiChannel
*/
int getMidiChannel() const noexcept { return midiChannel; }
/** Sets a mask to indicate which incoming midi channels should be represented by
key movements.
The mask is a set of bits, where bit 0 = midi channel 1, bit 1 = midi channel 2, etc.
If the MidiKeyboardState has a key down for any of the channels whose bits are set
in this mask, the on-screen keys will also go down.
By default, this mask is set to 0xffff (all channels displayed).
@see setMidiChannel
*/
void setMidiChannelsToDisplay (int midiChannelMask);
/** Returns the current set of midi channels represented by the component.
This is the value that was set with setMidiChannelsToDisplay().
*/
int getMidiChannelsToDisplay() const noexcept { return midiInChannelMask; }
//==============================================================================
/** Changes the width used to draw the white keys. */
void setKeyWidth (float widthInPixels);
/** Returns the width that was set by setKeyWidth(). */
float getKeyWidth() const noexcept { return keyWidth; }
/** Changes the keyboard's current direction. */
void setOrientation (Orientation newOrientation);
/** Returns the keyboard's current direction. */
Orientation getOrientation() const noexcept { return orientation; }
/** Sets the range of midi notes that the keyboard will be limited to.
By default the range is 0 to 127 (inclusive), but you can limit this if you
only want a restricted set of the keys to be shown.
Note that the values here are inclusive and must be between 0 and 127.
*/
void setAvailableRange (int lowestNote,
int highestNote);
/** Returns the first note in the available range.
@see setAvailableRange
*/
int getRangeStart() const noexcept { return rangeStart; }
/** Returns the last note in the available range.
@see setAvailableRange
*/
int getRangeEnd() const noexcept { return rangeEnd; }
/** If the keyboard extends beyond the size of the component, this will scroll
it to show the given key at the start.
Whenever the keyboard's position is changed, this will use the ChangeBroadcaster
base class to send a callback to any ChangeListeners that have been registered.
*/
void setLowestVisibleKey (int noteNumber);
/** Returns the number of the first key shown in the component.
@see setLowestVisibleKey
*/
int getLowestVisibleKey() const noexcept { return (int) firstKey; }
/** Returns the length of the black notes.
This will be their vertical or horizontal length, depending on the keyboard's orientation.
*/
int getBlackNoteLength() const noexcept { return blackNoteLength; }
/** If set to true, then scroll buttons will appear at either end of the keyboard
if there are too many notes to fit them all in the component at once.
*/
void setScrollButtonsVisible (bool canScroll);
//==============================================================================
/** A set of colour IDs to use to change the colour of various aspects of the keyboard.
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
methods.
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
*/
enum ColourIds
{
whiteNoteColourId = 0x1005000,
blackNoteColourId = 0x1005001,
keySeparatorLineColourId = 0x1005002,
mouseOverKeyOverlayColourId = 0x1005003, /**< This colour will be overlaid on the normal note colour. */
keyDownOverlayColourId = 0x1005004, /**< This colour will be overlaid on the normal note colour. */
textLabelColourId = 0x1005005,
upDownButtonBackgroundColourId = 0x1005006,
upDownButtonArrowColourId = 0x1005007,
shadowColourId = 0x1005008
};
/** Returns the position within the component of the left-hand edge of a key.
Depending on the keyboard's orientation, this may be a horizontal or vertical
distance, in either direction.
*/
int getKeyStartPosition (int midiNoteNumber) const;
/** Returns the key at a given coordinate. */
int getNoteAtPosition (Point<int> position);
//==============================================================================
/** Deletes all key-mappings.
@see setKeyPressForNote
*/
void clearKeyMappings();
/** Maps a key-press to a given note.
@param key the key that should trigger the note
@param midiNoteOffsetFromC how many semitones above C the triggered note should
be. The actual midi note that gets played will be
this value + (12 * the current base octave). To change
the base octave, see setKeyPressBaseOctave()
*/
void setKeyPressForNote (const KeyPress& key,
int midiNoteOffsetFromC);
/** Removes any key-mappings for a given note.
For a description of what the note number means, see setKeyPressForNote().
*/
void removeKeyPressForNote (int midiNoteOffsetFromC);
/** Changes the base note above which key-press-triggered notes are played.
The set of key-mappings that trigger notes can be moved up and down to cover
the entire scale using this method.
The value passed in is an octave number between 0 and 10 (inclusive), and
indicates which C is the base note to which the key-mapped notes are
relative.
*/
void setKeyPressBaseOctave (int newOctaveNumber);
/** This sets the octave number which is shown as the octave number for middle C.
This affects only the default implementation of getWhiteNoteText(), which
passes this octave number to MidiMessage::getMidiNoteName() in order to
get the note text. See MidiMessage::getMidiNoteName() for more info about
the parameter.
By default this value is set to 3.
@see getOctaveForMiddleC
*/
void setOctaveForMiddleC (int octaveNumForMiddleC);
/** This returns the value set by setOctaveForMiddleC().
@see setOctaveForMiddleC
*/
int getOctaveForMiddleC() const noexcept { return octaveNumForMiddleC; }
//==============================================================================
/** @internal */
void paint (Graphics&) override;
/** @internal */
void resized() override;
/** @internal */
void mouseMove (const MouseEvent&) override;
/** @internal */
void mouseDrag (const MouseEvent&) override;
/** @internal */
void mouseDown (const MouseEvent&) override;
/** @internal */
void mouseUp (const MouseEvent&) override;
/** @internal */
void mouseEnter (const MouseEvent&) override;
/** @internal */
void mouseExit (const MouseEvent&) override;
/** @internal */
void mouseWheelMove (const MouseEvent&, const MouseWheelDetails&) override;
/** @internal */
void timerCallback() override;
/** @internal */
bool keyStateChanged (bool isKeyDown) override;
/** @internal */
void focusLost (FocusChangeType) override;
/** @internal */
void handleNoteOn (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override;
/** @internal */
void handleNoteOff (MidiKeyboardState*, int midiChannel, int midiNoteNumber) override;
/** @internal */
void colourChanged() override;
protected:
//==============================================================================
/** Draws a white note in the given rectangle.
isOver indicates whether the mouse is over the key, isDown indicates whether the key is
currently pressed down.
When doing this, be sure to note the keyboard's orientation.
*/
virtual void drawWhiteNote (int midiNoteNumber,
Graphics& g,
int x, int y, int w, int h,
bool isDown, bool isOver,
const Colour& lineColour,
const Colour& textColour);
/** Draws a black note in the given rectangle.
isOver indicates whether the mouse is over the key, isDown indicates whether the key is
currently pressed down.
When doing this, be sure to note the keyboard's orientation.
*/
virtual void drawBlackNote (int midiNoteNumber,
Graphics& g,
int x, int y, int w, int h,
bool isDown, bool isOver,
const Colour& noteFillColour);
/** Allows text to be drawn on the white notes.
By default this is used to label the C in each octave, but could be used for other things.
@see setOctaveForMiddleC
*/
virtual String getWhiteNoteText (const int midiNoteNumber);
/** Draws the up and down buttons that change the base note. */
virtual void drawUpDownButton (Graphics& g, int w, int h,
const bool isMouseOver,
const bool isButtonPressed,
const bool movesOctavesUp);
/** Callback when the mouse is clicked on a key.
You could use this to do things like handle right-clicks on keys, etc.
Return true if you want the click to trigger the note, or false if you
want to handle it yourself and not have the note played.
@see mouseDraggedToKey
*/
virtual bool mouseDownOnKey (int midiNoteNumber, const MouseEvent& e);
/** Callback when the mouse is dragged from one key onto another.
@see mouseDownOnKey
*/
virtual void mouseDraggedToKey (int midiNoteNumber, const MouseEvent& e);
/** Callback when the mouse is released from a key.
@see mouseDownOnKey
*/
virtual void mouseUpOnKey (int midiNoteNumber, const MouseEvent& e);
/** Calculates the positon of a given midi-note.
This can be overridden to create layouts with custom key-widths.
@param midiNoteNumber the note to find
@param keyWidth the desired width in pixels of one key - see setKeyWidth()
@param x the x position of the left-hand edge of the key (this method
always works in terms of a horizontal keyboard)
@param w the width of the key
*/
virtual void getKeyPosition (int midiNoteNumber, float keyWidth,
int& x, int& w) const;
private:
//==============================================================================
friend class MidiKeyboardUpDownButton;
MidiKeyboardState& state;
int xOffset, blackNoteLength;
float keyWidth;
Orientation orientation;
int midiChannel, midiInChannelMask;
float velocity;
Array<int> mouseOverNotes, mouseDownNotes;
BigInteger keysPressed, keysCurrentlyDrawnDown;
bool shouldCheckState;
int rangeStart, rangeEnd;
float firstKey;
bool canScroll, useMousePositionForVelocity, shouldCheckMousePos;
ScopedPointer<Button> scrollDown, scrollUp;
Array<KeyPress> keyPresses;
Array<int> keyPressNotes;
int keyMappingOctave, octaveNumForMiddleC;
static const uint8 whiteNotes[];
static const uint8 blackNotes[];
void getKeyPos (int midiNoteNumber, int& x, int& w) const;
int xyToNote (Point<int>, float& mousePositionVelocity);
int remappedXYToNote (Point<int>, float& mousePositionVelocity) const;
void resetAnyKeysInUse();
void updateNoteUnderMouse (Point<int>, bool isDown, int fingerNum);
void updateNoteUnderMouse (const MouseEvent&, bool isDown);
void repaintNote (const int midiNoteNumber);
void setLowestVisibleKeyFloat (float noteNumber);
Rectangle<int> getWhiteNotePos (int noteNumber) const;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiKeyboardComponent)
};
#endif // JUCE_MIDIKEYBOARDCOMPONENT_H_INCLUDED

View file

@ -0,0 +1,51 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE 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.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#if defined (JUCE_AUDIO_UTILS_H_INCLUDED) && ! JUCE_AMALGAMATED_INCLUDE
/* When you add this cpp file to your project, you mustn't include it in a file where you've
already included any other headers - just put it inside a file on its own, possibly with your config
flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix
header files that the compiler may be using.
*/
#error "Incorrect use of JUCE cpp file"
#endif
// Your project must contain an AppConfig.h file with your project-specific settings in it,
// and your header search path must make it accessible to the module's files.
#include "AppConfig.h"
#include "../juce_core/native/juce_BasicNativeHeaders.h"
#include "juce_audio_utils.h"
namespace juce
{
#include "gui/juce_AudioDeviceSelectorComponent.cpp"
#include "gui/juce_AudioThumbnail.cpp"
#include "gui/juce_AudioThumbnailCache.cpp"
#include "gui/juce_MidiKeyboardComponent.cpp"
#include "gui/juce_AudioAppComponent.cpp"
#include "players/juce_AudioProcessorPlayer.cpp"
}

View file

@ -0,0 +1,47 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE 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.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_AUDIO_UTILS_H_INCLUDED
#define JUCE_AUDIO_UTILS_H_INCLUDED
#include "../juce_gui_basics/juce_gui_basics.h"
#include "../juce_audio_devices/juce_audio_devices.h"
#include "../juce_audio_formats/juce_audio_formats.h"
#include "../juce_audio_processors/juce_audio_processors.h"
//=============================================================================
namespace juce
{
#include "gui/juce_AudioDeviceSelectorComponent.h"
#include "gui/juce_AudioThumbnailBase.h"
#include "gui/juce_AudioThumbnail.h"
#include "gui/juce_AudioThumbnailCache.h"
#include "gui/juce_MidiKeyboardComponent.h"
#include "gui/juce_AudioAppComponent.h"
#include "players/juce_AudioProcessorPlayer.h"
}
#endif // JUCE_AUDIO_UTILS_H_INCLUDED

View file

@ -0,0 +1,25 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE 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.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#include "juce_audio_utils.cpp"

View file

@ -0,0 +1,21 @@
{
"id": "juce_audio_utils",
"name": "JUCE extra audio utility classes",
"version": "3.0.8",
"description": "Classes for audio-related GUI and miscellaneous tasks.",
"website": "http://www.juce.com/juce",
"license": "GPL/Commercial",
"dependencies": [ { "id": "juce_gui_basics", "version": "matching" },
{ "id": "juce_audio_devices", "version": "matching" },
{ "id": "juce_audio_processors", "version": "matching" },
{ "id": "juce_audio_formats", "version": "matching" } ],
"include": "juce_audio_utils.h",
"compile": [ { "file": "juce_audio_utils.cpp", "target": "! xcode" },
{ "file": "juce_audio_utils.mm", "target": "xcode" } ],
"browse": [ "gui/*",
"players/*" ]
}

View file

@ -0,0 +1,183 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE 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.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
AudioProcessorPlayer::AudioProcessorPlayer()
: processor (nullptr),
sampleRate (0),
blockSize (0),
isPrepared (false),
numInputChans (0),
numOutputChans (0)
{
}
AudioProcessorPlayer::~AudioProcessorPlayer()
{
setProcessor (nullptr);
}
//==============================================================================
void AudioProcessorPlayer::setProcessor (AudioProcessor* const processorToPlay)
{
if (processor != processorToPlay)
{
if (processorToPlay != nullptr && sampleRate > 0 && blockSize > 0)
{
processorToPlay->setPlayConfigDetails (numInputChans, numOutputChans, sampleRate, blockSize);
processorToPlay->prepareToPlay (sampleRate, blockSize);
}
AudioProcessor* oldOne;
{
const ScopedLock sl (lock);
oldOne = isPrepared ? processor : nullptr;
processor = processorToPlay;
isPrepared = true;
}
if (oldOne != nullptr)
oldOne->releaseResources();
}
}
//==============================================================================
void AudioProcessorPlayer::audioDeviceIOCallback (const float** const inputChannelData,
const int numInputChannels,
float** const outputChannelData,
const int numOutputChannels,
const int numSamples)
{
// these should have been prepared by audioDeviceAboutToStart()...
jassert (sampleRate > 0 && blockSize > 0);
incomingMidi.clear();
messageCollector.removeNextBlockOfMessages (incomingMidi, numSamples);
int totalNumChans = 0;
if (numInputChannels > numOutputChannels)
{
// if there aren't enough output channels for the number of
// inputs, we need to create some temporary extra ones (can't
// use the input data in case it gets written to)
tempBuffer.setSize (numInputChannels - numOutputChannels, numSamples,
false, false, true);
for (int i = 0; i < numOutputChannels; ++i)
{
channels[totalNumChans] = outputChannelData[i];
memcpy (channels[totalNumChans], inputChannelData[i], sizeof (float) * (size_t) numSamples);
++totalNumChans;
}
for (int i = numOutputChannels; i < numInputChannels; ++i)
{
channels[totalNumChans] = tempBuffer.getWritePointer (i - numOutputChannels);
memcpy (channels[totalNumChans], inputChannelData[i], sizeof (float) * (size_t) numSamples);
++totalNumChans;
}
}
else
{
for (int i = 0; i < numInputChannels; ++i)
{
channels[totalNumChans] = outputChannelData[i];
memcpy (channels[totalNumChans], inputChannelData[i], sizeof (float) * (size_t) numSamples);
++totalNumChans;
}
for (int i = numInputChannels; i < numOutputChannels; ++i)
{
channels[totalNumChans] = outputChannelData[i];
zeromem (channels[totalNumChans], sizeof (float) * (size_t) numSamples);
++totalNumChans;
}
}
AudioSampleBuffer buffer (channels, totalNumChans, numSamples);
{
const ScopedLock sl (lock);
if (processor != nullptr)
{
const ScopedLock sl2 (processor->getCallbackLock());
if (! processor->isSuspended())
{
processor->processBlock (buffer, incomingMidi);
return;
}
}
}
for (int i = 0; i < numOutputChannels; ++i)
FloatVectorOperations::clear (outputChannelData[i], numSamples);
}
void AudioProcessorPlayer::audioDeviceAboutToStart (AudioIODevice* const device)
{
const double newSampleRate = device->getCurrentSampleRate();
const int newBlockSize = device->getCurrentBufferSizeSamples();
const int numChansIn = device->getActiveInputChannels().countNumberOfSetBits();
const int numChansOut = device->getActiveOutputChannels().countNumberOfSetBits();
const ScopedLock sl (lock);
sampleRate = newSampleRate;
blockSize = newBlockSize;
numInputChans = numChansIn;
numOutputChans = numChansOut;
messageCollector.reset (sampleRate);
channels.calloc ((size_t) jmax (numChansIn, numChansOut) + 2);
if (processor != nullptr)
{
if (isPrepared)
processor->releaseResources();
AudioProcessor* const oldProcessor = processor;
setProcessor (nullptr);
setProcessor (oldProcessor);
}
}
void AudioProcessorPlayer::audioDeviceStopped()
{
const ScopedLock sl (lock);
if (processor != nullptr && isPrepared)
processor->releaseResources();
sampleRate = 0.0;
blockSize = 0;
isPrepared = false;
tempBuffer.setSize (1, 1);
}
void AudioProcessorPlayer::handleIncomingMidiMessage (MidiInput*, const MidiMessage& message)
{
messageCollector.addMessageToQueue (message);
}

View file

@ -0,0 +1,97 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE 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.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_AUDIOPROCESSORPLAYER_H_INCLUDED
#define JUCE_AUDIOPROCESSORPLAYER_H_INCLUDED
//==============================================================================
/**
An AudioIODeviceCallback object which streams audio through an AudioProcessor.
To use one of these, just make it the callback used by your AudioIODevice, and
give it a processor to use by calling setProcessor().
It's also a MidiInputCallback, so you can connect it to both an audio and midi
input to send both streams through the processor.
@see AudioProcessor, AudioProcessorGraph
*/
class JUCE_API AudioProcessorPlayer : public AudioIODeviceCallback,
public MidiInputCallback
{
public:
//==============================================================================
AudioProcessorPlayer();
/** Destructor. */
virtual ~AudioProcessorPlayer();
//==============================================================================
/** Sets the processor that should be played.
The processor that is passed in will not be deleted or owned by this object.
To stop anything playing, pass a nullptr to this method.
*/
void setProcessor (AudioProcessor* processorToPlay);
/** Returns the current audio processor that is being played. */
AudioProcessor* getCurrentProcessor() const noexcept { return processor; }
/** Returns a midi message collector that you can pass midi messages to if you
want them to be injected into the midi stream that is being sent to the
processor.
*/
MidiMessageCollector& getMidiMessageCollector() noexcept { return messageCollector; }
//==============================================================================
/** @internal */
void audioDeviceIOCallback (const float**, int, float**, int, int) override;
/** @internal */
void audioDeviceAboutToStart (AudioIODevice*) override;
/** @internal */
void audioDeviceStopped() override;
/** @internal */
void handleIncomingMidiMessage (MidiInput*, const MidiMessage&) override;
private:
//==============================================================================
AudioProcessor* processor;
CriticalSection lock;
double sampleRate;
int blockSize;
bool isPrepared;
int numInputChans, numOutputChans;
HeapBlock<float*> channels;
AudioSampleBuffer tempBuffer;
MidiBuffer incomingMidi;
MidiMessageCollector messageCollector;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioProcessorPlayer)
};
#endif // JUCE_AUDIOPROCESSORPLAYER_H_INCLUDED