git subrepo clone --branch=sono6good https://github.com/essej/JUCE.git deps/juce
subrepo: subdir: "deps/juce" merged: "b13f9084e" upstream: origin: "https://github.com/essej/JUCE.git" branch: "sono6good" commit: "b13f9084e" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo.git" commit: "2f68596"
This commit is contained in:
172
deps/juce/modules/juce_audio_utils/audio_cd/juce_AudioCDBurner.h
vendored
Normal file
172
deps/juce/modules/juce_audio_utils/audio_cd/juce_AudioCDBurner.h
vendored
Normal file
@ -0,0 +1,172 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#if JUCE_USE_CDBURNER || DOXYGEN
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class AudioCDBurner : public ChangeBroadcaster
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Returns a list of available optical drives.
|
||||
|
||||
Use openDevice() to open one of the items from this list.
|
||||
*/
|
||||
static StringArray findAvailableDevices();
|
||||
|
||||
/** Tries to open one of the optical drives.
|
||||
|
||||
The deviceIndex is an index into the array returned by findAvailableDevices().
|
||||
*/
|
||||
static AudioCDBurner* openDevice (const int deviceIndex);
|
||||
|
||||
/** Destructor. */
|
||||
~AudioCDBurner();
|
||||
|
||||
//==============================================================================
|
||||
enum DiskState
|
||||
{
|
||||
unknown, /**< An error condition, if the device isn't responding. */
|
||||
trayOpen, /**< The drive is currently open. Note that a slot-loading drive
|
||||
may seem to be permanently open. */
|
||||
noDisc, /**< The drive has no disk in it. */
|
||||
writableDiskPresent, /**< The drive contains a writeable disk. */
|
||||
readOnlyDiskPresent /**< The drive contains a read-only disk. */
|
||||
};
|
||||
|
||||
/** Returns the current status of the device.
|
||||
|
||||
To get informed when the drive's status changes, attach a ChangeListener to
|
||||
the AudioCDBurner.
|
||||
*/
|
||||
DiskState getDiskState() const;
|
||||
|
||||
/** Returns true if there's a writable disk in the drive. */
|
||||
bool isDiskPresent() const;
|
||||
|
||||
/** Sends an eject signal to the drive.
|
||||
The eject will happen asynchronously, so you can use getDiskState() and
|
||||
waitUntilStateChange() to monitor its progress.
|
||||
*/
|
||||
bool openTray();
|
||||
|
||||
/** Blocks the current thread until the drive's state changes, or until the timeout expires.
|
||||
@returns the device's new state
|
||||
*/
|
||||
DiskState waitUntilStateChange (int timeOutMilliseconds);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the set of possible write speeds that the device can handle.
|
||||
These are as a multiple of 'normal' speed, so e.g. '24x' returns 24, etc.
|
||||
Note that if there's no media present in the drive, this value may be unavailable!
|
||||
@see setWriteSpeed, getWriteSpeed
|
||||
*/
|
||||
Array<int> getAvailableWriteSpeeds() const;
|
||||
|
||||
//==============================================================================
|
||||
/** Tries to enable or disable buffer underrun safety on devices that support it.
|
||||
@returns true if it's now enabled. If the device doesn't support it, this
|
||||
will always return false.
|
||||
*/
|
||||
bool setBufferUnderrunProtection (bool shouldBeEnabled);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the number of free blocks on the disk.
|
||||
|
||||
There are 75 blocks per second, at 44100Hz.
|
||||
*/
|
||||
int getNumAvailableAudioBlocks() const;
|
||||
|
||||
/** Adds a track to be written.
|
||||
|
||||
The source passed-in here will be kept by this object, and it will
|
||||
be used and deleted at some point in the future, either during the
|
||||
burn() method or when this AudioCDBurner object is deleted. Your caller
|
||||
method shouldn't keep a reference to it or use it again after passing
|
||||
it in here.
|
||||
*/
|
||||
bool addAudioTrack (AudioSource* source, int numSamples);
|
||||
|
||||
//==============================================================================
|
||||
/** Receives progress callbacks during a cd-burn operation.
|
||||
@see AudioCDBurner::burn()
|
||||
*/
|
||||
class BurnProgressListener
|
||||
{
|
||||
public:
|
||||
BurnProgressListener() noexcept {}
|
||||
virtual ~BurnProgressListener() {}
|
||||
|
||||
/** Called at intervals to report on the progress of the AudioCDBurner.
|
||||
|
||||
To cancel the burn, return true from this method.
|
||||
*/
|
||||
virtual bool audioCDBurnProgress (float proportionComplete) = 0;
|
||||
};
|
||||
|
||||
/** Runs the burn process.
|
||||
This method will block until the operation is complete.
|
||||
|
||||
@param listener the object to receive callbacks about progress
|
||||
@param ejectDiscAfterwards whether to eject the disk after the burn completes
|
||||
@param performFakeBurnForTesting if true, no data will actually be written to the disk
|
||||
@param writeSpeed one of the write speeds from getAvailableWriteSpeeds(), or
|
||||
0 or less to mean the fastest speed.
|
||||
*/
|
||||
String burn (BurnProgressListener* listener,
|
||||
bool ejectDiscAfterwards,
|
||||
bool performFakeBurnForTesting,
|
||||
int writeSpeed);
|
||||
|
||||
/** If a burn operation is currently in progress, this tells it to stop
|
||||
as soon as possible.
|
||||
|
||||
It's also possible to stop the burn process by returning true from
|
||||
BurnProgressListener::audioCDBurnProgress()
|
||||
*/
|
||||
void abortBurn();
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
AudioCDBurner (const int deviceIndex);
|
||||
|
||||
class Pimpl;
|
||||
std::unique_ptr<Pimpl> pimpl;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioCDBurner)
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
63
deps/juce/modules/juce_audio_utils/audio_cd/juce_AudioCDReader.cpp
vendored
Normal file
63
deps/juce/modules/juce_audio_utils/audio_cd/juce_AudioCDReader.cpp
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#if JUCE_USE_CDREADER
|
||||
|
||||
int AudioCDReader::getNumTracks() const
|
||||
{
|
||||
return trackStartSamples.size() - 1;
|
||||
}
|
||||
|
||||
int AudioCDReader::getPositionOfTrackStart (int trackNum) const
|
||||
{
|
||||
return trackStartSamples [trackNum];
|
||||
}
|
||||
|
||||
const Array<int>& AudioCDReader::getTrackOffsets() const
|
||||
{
|
||||
return trackStartSamples;
|
||||
}
|
||||
|
||||
int AudioCDReader::getCDDBId()
|
||||
{
|
||||
int checksum = 0;
|
||||
const int numTracks = getNumTracks();
|
||||
|
||||
for (int i = 0; i < numTracks; ++i)
|
||||
for (int offset = (trackStartSamples.getUnchecked(i) + 88200) / 44100; offset > 0; offset /= 10)
|
||||
checksum += offset % 10;
|
||||
|
||||
const int length = (trackStartSamples.getLast() - trackStartSamples.getFirst()) / 44100;
|
||||
|
||||
// CCLLLLTT: checksum, length, tracks
|
||||
return ((checksum & 0xff) << 24) | (length << 8) | numTracks;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
177
deps/juce/modules/juce_audio_utils/audio_cd/juce_AudioCDReader.h
vendored
Normal file
177
deps/juce/modules/juce_audio_utils/audio_cd/juce_AudioCDReader.h
vendored
Normal file
@ -0,0 +1,177 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#if JUCE_USE_CDREADER || DOXYGEN
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A type of AudioFormatReader that reads from an audio CD.
|
||||
|
||||
One of these can be used to read a CD as if it's one big audio stream. Use the
|
||||
getPositionOfTrackStart() method to find where the individual tracks are
|
||||
within the stream.
|
||||
|
||||
@see AudioFormatReader
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class JUCE_API AudioCDReader : public AudioFormatReader
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Returns a list of names of Audio CDs currently available for reading.
|
||||
|
||||
If there's a CD drive but no CD in it, this might return an empty list, or
|
||||
possibly a device that can be opened but which has no tracks, depending
|
||||
on the platform.
|
||||
|
||||
@see createReaderForCD
|
||||
*/
|
||||
static StringArray getAvailableCDNames();
|
||||
|
||||
/** Tries to create an AudioFormatReader that can read from an Audio CD.
|
||||
|
||||
@param index the index of one of the available CDs - use getAvailableCDNames()
|
||||
to find out how many there are.
|
||||
@returns a new AudioCDReader object, or nullptr if it couldn't be created. The
|
||||
caller will be responsible for deleting the object returned.
|
||||
*/
|
||||
static AudioCDReader* createReaderForCD (const int index);
|
||||
|
||||
//==============================================================================
|
||||
/** Destructor. */
|
||||
~AudioCDReader() override;
|
||||
|
||||
/** Implementation of the AudioFormatReader method. */
|
||||
bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer,
|
||||
int64 startSampleInFile, int numSamples) override;
|
||||
|
||||
/** Checks whether the CD has been removed from the drive. */
|
||||
bool isCDStillPresent() const;
|
||||
|
||||
/** Returns the total number of tracks (audio + data). */
|
||||
int getNumTracks() const;
|
||||
|
||||
/** Finds the sample offset of the start of a track.
|
||||
@param trackNum the track number, where trackNum = 0 is the first track
|
||||
and trackNum = getNumTracks() means the end of the CD.
|
||||
*/
|
||||
int getPositionOfTrackStart (int trackNum) const;
|
||||
|
||||
/** Returns true if a given track is an audio track.
|
||||
@param trackNum the track number, where 0 is the first track.
|
||||
*/
|
||||
bool isTrackAudio (int trackNum) const;
|
||||
|
||||
/** Returns an array of sample offsets for the start of each track, followed by
|
||||
the sample position of the end of the CD.
|
||||
*/
|
||||
const Array<int>& getTrackOffsets() const;
|
||||
|
||||
/** Refreshes the object's table of contents.
|
||||
|
||||
If the disc has been ejected and a different one put in since this
|
||||
object was created, this will cause it to update its idea of how many tracks
|
||||
there are, etc.
|
||||
*/
|
||||
void refreshTrackLengths();
|
||||
|
||||
/** Enables scanning for indexes within tracks.
|
||||
@see getLastIndex
|
||||
*/
|
||||
void enableIndexScanning (bool enabled);
|
||||
|
||||
/** Returns the index number found during the last read() call.
|
||||
|
||||
Index scanning is turned off by default - turn it on with enableIndexScanning().
|
||||
|
||||
Then when the read() method is called, if it comes across an index within that
|
||||
block, the index number is stored and returned by this method.
|
||||
|
||||
Some devices might not support indexes, of course.
|
||||
|
||||
(If you don't know what CD indexes are, it's unlikely you'll ever need them).
|
||||
|
||||
@see enableIndexScanning
|
||||
*/
|
||||
int getLastIndex() const;
|
||||
|
||||
/** Scans a track to find the position of any indexes within it.
|
||||
@param trackNumber the track to look in, where 0 is the first track on the disc
|
||||
@returns an array of sample positions of any index points found (not including
|
||||
the index that marks the start of the track)
|
||||
*/
|
||||
Array<int> findIndexesInTrack (const int trackNumber);
|
||||
|
||||
/** Returns the CDDB id number for the CD.
|
||||
It's not a great way of identifying a disc, but it's traditional.
|
||||
*/
|
||||
int getCDDBId();
|
||||
|
||||
/** Tries to eject the disk.
|
||||
Ejecting the disk might not actually be possible, e.g. if some other process is using it.
|
||||
*/
|
||||
void ejectDisk();
|
||||
|
||||
//==============================================================================
|
||||
enum
|
||||
{
|
||||
framesPerSecond = 75,
|
||||
samplesPerFrame = 44100 / framesPerSecond
|
||||
};
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
Array<int> trackStartSamples;
|
||||
|
||||
#if JUCE_MAC
|
||||
File volumeDir;
|
||||
Array<File> tracks;
|
||||
int currentReaderTrack;
|
||||
std::unique_ptr<AudioFormatReader> reader;
|
||||
AudioCDReader (const File& volume);
|
||||
|
||||
#elif JUCE_WINDOWS
|
||||
bool audioTracks [100];
|
||||
void* handle;
|
||||
MemoryBlock buffer;
|
||||
bool indexingEnabled;
|
||||
int lastIndex, firstFrameInBuffer, samplesInBuffer;
|
||||
AudioCDReader (void* handle);
|
||||
int getIndexAt (int samplePos);
|
||||
|
||||
#elif JUCE_LINUX || JUCE_BSD
|
||||
AudioCDReader();
|
||||
#endif
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioCDReader)
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
89
deps/juce/modules/juce_audio_utils/gui/juce_AudioAppComponent.cpp
vendored
Normal file
89
deps/juce/modules/juce_audio_utils/gui/juce_AudioAppComponent.cpp
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
AudioAppComponent::AudioAppComponent()
|
||||
: deviceManager (defaultDeviceManager),
|
||||
usingCustomDeviceManager (false)
|
||||
{
|
||||
}
|
||||
|
||||
AudioAppComponent::AudioAppComponent (AudioDeviceManager& adm)
|
||||
: deviceManager (adm),
|
||||
usingCustomDeviceManager (true)
|
||||
{
|
||||
}
|
||||
|
||||
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, const XmlElement* const xml)
|
||||
{
|
||||
String audioError;
|
||||
|
||||
if (usingCustomDeviceManager && xml == nullptr)
|
||||
{
|
||||
auto setup = deviceManager.getAudioDeviceSetup();
|
||||
|
||||
if (setup.inputChannels.countNumberOfSetBits() != numInputChannels
|
||||
|| setup.outputChannels.countNumberOfSetBits() != numOutputChannels)
|
||||
{
|
||||
setup.inputChannels.clear();
|
||||
setup.outputChannels.clear();
|
||||
|
||||
setup.inputChannels.setRange (0, numInputChannels, true);
|
||||
setup.outputChannels.setRange (0, numOutputChannels, true);
|
||||
|
||||
audioError = deviceManager.setAudioDeviceSetup (setup, false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
audioError = deviceManager.initialise (numInputChannels, numOutputChannels, xml, true);
|
||||
}
|
||||
|
||||
jassert (audioError.isEmpty());
|
||||
|
||||
deviceManager.addAudioCallback (&audioSourcePlayer);
|
||||
audioSourcePlayer.setSource (this);
|
||||
}
|
||||
|
||||
void AudioAppComponent::shutdownAudio()
|
||||
{
|
||||
audioSourcePlayer.setSource (nullptr);
|
||||
deviceManager.removeAudioCallback (&audioSourcePlayer);
|
||||
|
||||
// other audio callbacks may still be using the device
|
||||
if (! usingCustomDeviceManager)
|
||||
deviceManager.closeAudioDevice();
|
||||
}
|
||||
|
||||
} // namespace juce
|
135
deps/juce/modules/juce_audio_utils/gui/juce_AudioAppComponent.h
vendored
Normal file
135
deps/juce/modules/juce_audio_utils/gui/juce_AudioAppComponent.h
vendored
Normal file
@ -0,0 +1,135 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A base class for writing audio apps that stream from the audio i/o devices.
|
||||
Conveniently combines a Component with an AudioSource to provide a starting
|
||||
point for your audio applications.
|
||||
|
||||
A subclass can inherit from this and implement just a few methods such as
|
||||
getNextAudioBlock(). The base class provides a basic AudioDeviceManager object
|
||||
and runs audio through the default output device.
|
||||
|
||||
An application should only create one global instance of this object and multiple
|
||||
classes should not inherit from this.
|
||||
|
||||
This class should not be inherited when creating a plug-in as the host will
|
||||
handle audio streams from hardware devices.
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class JUCE_API AudioAppComponent : public Component,
|
||||
public AudioSource
|
||||
{
|
||||
public:
|
||||
AudioAppComponent();
|
||||
AudioAppComponent (AudioDeviceManager&);
|
||||
|
||||
~AudioAppComponent() override;
|
||||
|
||||
/** A subclass should call this from their constructor, to set up the audio. */
|
||||
void setAudioChannels (int numInputChannels, int numOutputChannels, const XmlElement* const storedSettings = nullptr);
|
||||
|
||||
/** 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 'unprepared'
|
||||
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
|
||||
*/
|
||||
void prepareToPlay (int samplesPerBlockExpected,
|
||||
double sampleRate) override = 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
|
||||
*/
|
||||
void releaseResources() override = 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
|
||||
*/
|
||||
void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override = 0;
|
||||
|
||||
/** Shuts down the audio device and clears the audio source.
|
||||
|
||||
This method should be called in the destructor of the derived class
|
||||
otherwise an assertion will be triggered.
|
||||
*/
|
||||
void shutdownAudio();
|
||||
|
||||
|
||||
AudioDeviceManager& deviceManager;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
AudioDeviceManager defaultDeviceManager;
|
||||
AudioSourcePlayer audioSourcePlayer;
|
||||
bool usingCustomDeviceManager;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioAppComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
1338
deps/juce/modules/juce_audio_utils/gui/juce_AudioDeviceSelectorComponent.cpp
vendored
Normal file
1338
deps/juce/modules/juce_audio_utils/gui/juce_AudioDeviceSelectorComponent.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
121
deps/juce/modules/juce_audio_utils/gui/juce_AudioDeviceSelectorComponent.h
vendored
Normal file
121
deps/juce/modules/juce_audio_utils/gui/juce_AudioDeviceSelectorComponent.h
vendored
Normal file
@ -0,0 +1,121 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
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
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class JUCE_API AudioDeviceSelectorComponent : public Component,
|
||||
private ChangeListener,
|
||||
private Timer
|
||||
{
|
||||
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() override;
|
||||
|
||||
/** 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; }
|
||||
|
||||
/** Returns the ListBox that's being used to show the midi inputs, or nullptr if there isn't one. */
|
||||
ListBox* getMidiInputSelectorListBox() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
void timerCallback() override;
|
||||
void handleBluetoothButton();
|
||||
void updateDeviceType();
|
||||
void updateMidiOutput();
|
||||
void changeListenerCallback (ChangeBroadcaster*) override;
|
||||
void updateAllControls();
|
||||
|
||||
std::unique_ptr<ComboBox> deviceTypeDropDown;
|
||||
std::unique_ptr<Label> deviceTypeDropDownLabel;
|
||||
std::unique_ptr<Component> audioDeviceSettingsComp;
|
||||
String audioDeviceSettingsCompType;
|
||||
int itemHeight = 0;
|
||||
const int minOutputChannels, maxOutputChannels, minInputChannels, maxInputChannels;
|
||||
const bool showChannelsAsStereoPairs;
|
||||
const bool hideAdvancedOptionsWithButton;
|
||||
|
||||
class MidiInputSelectorComponentListBox;
|
||||
Array<MidiDeviceInfo> currentMidiOutputs;
|
||||
std::unique_ptr<MidiInputSelectorComponentListBox> midiInputsList;
|
||||
std::unique_ptr<ComboBox> midiOutputSelector;
|
||||
std::unique_ptr<Label> midiInputsLabel, midiOutputLabel;
|
||||
std::unique_ptr<TextButton> bluetoothButton;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioDeviceSelectorComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
826
deps/juce/modules/juce_audio_utils/gui/juce_AudioThumbnail.cpp
vendored
Normal file
826
deps/juce/modules/juce_audio_utils/gui/juce_AudioThumbnail.cpp
vendored
Normal file
@ -0,0 +1,826 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
struct AudioThumbnail::MinMaxValue
|
||||
{
|
||||
MinMaxValue() noexcept
|
||||
{
|
||||
values[0] = 0;
|
||||
values[1] = 0;
|
||||
}
|
||||
|
||||
inline void set (const int8 newMin, const int8 newMax) noexcept
|
||||
{
|
||||
values[0] = newMin;
|
||||
values[1] = newMax;
|
||||
}
|
||||
|
||||
inline int8 getMinValue() const noexcept { return values[0]; }
|
||||
inline int8 getMaxValue() const noexcept { return values[1]; }
|
||||
|
||||
inline void setFloat (Range<float> newRange) noexcept
|
||||
{
|
||||
// Workaround for an ndk armeabi compiler bug which crashes on signed saturation
|
||||
#if JUCE_ANDROID
|
||||
Range<float> limitedRange (jlimit (-1.0f, 1.0f, newRange.getStart()),
|
||||
jlimit (-1.0f, 1.0f, newRange.getEnd()));
|
||||
values[0] = (int8) (limitedRange.getStart() * 127.0f);
|
||||
values[1] = (int8) (limitedRange.getEnd() * 127.0f);
|
||||
#else
|
||||
values[0] = (int8) jlimit (-128, 127, roundToInt (newRange.getStart() * 127.0f));
|
||||
values[1] = (int8) jlimit (-128, 127, roundToInt (newRange.getEnd() * 127.0f));
|
||||
#endif
|
||||
|
||||
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:
|
||||
int8 values[2];
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class AudioThumbnail::LevelDataSource : public TimeSliceClient
|
||||
{
|
||||
public:
|
||||
LevelDataSource (AudioThumbnail& thumb, AudioFormatReader* newReader, int64 hash)
|
||||
: hashCode (hash), owner (thumb), reader (newReader)
|
||||
{
|
||||
}
|
||||
|
||||
LevelDataSource (AudioThumbnail& thumb, InputSource* src)
|
||||
: hashCode (src->hashCode()), owner (thumb), source (src)
|
||||
{
|
||||
}
|
||||
|
||||
~LevelDataSource() override
|
||||
{
|
||||
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.reset();
|
||||
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, {}, (int) reader->numChannels - levels.size());
|
||||
|
||||
reader->readMaxLevels (startSample, numSamples, levels.getRawDataPointer(), (int) reader->numChannels);
|
||||
|
||||
lastReaderUseTime = Time::getMillisecondCounter();
|
||||
}
|
||||
}
|
||||
|
||||
void releaseResources()
|
||||
{
|
||||
const ScopedLock sl (readerLock);
|
||||
reader.reset();
|
||||
}
|
||||
|
||||
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 = 0, numSamplesFinished = 0;
|
||||
double sampleRate = 0;
|
||||
unsigned int numChannels = 0;
|
||||
int64 hashCode = 0;
|
||||
|
||||
private:
|
||||
AudioThumbnail& owner;
|
||||
std::unique_ptr<InputSource> source;
|
||||
std::unique_ptr<AudioFormatReader> reader;
|
||||
CriticalSection readerLock;
|
||||
std::atomic<uint32> lastReaderUseTime { 0 };
|
||||
|
||||
void createReader()
|
||||
{
|
||||
if (reader == nullptr && source != nullptr)
|
||||
if (auto* audioFileStream = source->createInputStream())
|
||||
reader.reset (owner.formatManagerToUse.createReaderFor (std::unique_ptr<InputStream> (audioFileStream)));
|
||||
}
|
||||
|
||||
bool readNextBlock()
|
||||
{
|
||||
jassert (reader != nullptr);
|
||||
|
||||
if (! isFullyLoaded())
|
||||
{
|
||||
auto numToDo = (int) jmin (256 * (int64) owner.samplesPerThumbSample, lengthInSamples - numSamplesFinished);
|
||||
|
||||
if (numToDo > 0)
|
||||
{
|
||||
auto startSample = numSamplesFinished;
|
||||
|
||||
auto firstThumbIndex = sampleToThumbSample (startSample);
|
||||
auto lastThumbIndex = sampleToThumbSample (startSample + numToDo);
|
||||
auto numThumbSamps = lastThumbIndex - firstThumbIndex;
|
||||
|
||||
HeapBlock<MinMaxValue> levelData ((unsigned int) 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, (int) 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 (int numThumbSamples)
|
||||
{
|
||||
ensureSize (numThumbSamples);
|
||||
}
|
||||
|
||||
inline MinMaxValue* getData (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);
|
||||
|
||||
int8 mx = -128;
|
||||
int8 mn = 127;
|
||||
|
||||
while (startSample <= endSample)
|
||||
{
|
||||
auto& 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* values, int startIndex, int numValues)
|
||||
{
|
||||
resetPeak();
|
||||
|
||||
if (startIndex + numValues > data.size())
|
||||
ensureSize (startIndex + numValues);
|
||||
|
||||
auto* 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 (auto& s : data)
|
||||
{
|
||||
auto peak = s.getPeak();
|
||||
|
||||
if (peak > peakLevel)
|
||||
peakLevel = peak;
|
||||
}
|
||||
}
|
||||
|
||||
return peakLevel;
|
||||
}
|
||||
|
||||
private:
|
||||
Array<MinMaxValue> data;
|
||||
int peakLevel = -1;
|
||||
|
||||
void ensureSize (int thumbSamples)
|
||||
{
|
||||
auto extraNeeded = thumbSamples - data.size();
|
||||
|
||||
if (extraNeeded > 0)
|
||||
data.insertMultiple (-1, MinMaxValue(), extraNeeded);
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class AudioThumbnail::CachedWindow
|
||||
{
|
||||
public:
|
||||
CachedWindow() {}
|
||||
|
||||
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))
|
||||
{
|
||||
auto clip = g.getClipBounds().getIntersection (area.withWidth (jmin (numSamplesCached, area.getWidth())));
|
||||
|
||||
if (! clip.isEmpty())
|
||||
{
|
||||
auto topY = (float) area.getY();
|
||||
auto bottomY = (float) area.getBottom();
|
||||
auto midY = (topY + bottomY) * 0.5f;
|
||||
auto vscale = verticalZoomFactor * (bottomY - topY) / 256.0f;
|
||||
|
||||
auto* cacheData = getData (channelNum, clip.getX() - area.getX());
|
||||
|
||||
RectangleList<float> waveform;
|
||||
waveform.ensureStorageAllocated (clip.getWidth());
|
||||
|
||||
auto x = (float) clip.getX();
|
||||
|
||||
for (int w = clip.getWidth(); --w >= 0;)
|
||||
{
|
||||
if (cacheData->isNonZero())
|
||||
{
|
||||
auto top = jmax (midY - cacheData->getMaxValue() * vscale - 0.3f, topY);
|
||||
auto 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 = 0, cachedTimePerPixel = 0;
|
||||
int numChannelsCached = 0, numSamplesCached = 0;
|
||||
bool cacheNeedsRefilling = true;
|
||||
|
||||
bool refillCache (int numSamples, double startTime, double endTime,
|
||||
double rate, int numChans, int sampsPerThumbSample,
|
||||
LevelDataSource* levelData, const OwnedArray<ThumbData>& chans)
|
||||
{
|
||||
auto 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)
|
||||
{
|
||||
auto sample = roundToInt (startTime * rate);
|
||||
Array<Range<float>> levels;
|
||||
|
||||
int i;
|
||||
for (i = 0; i < numSamples; ++i)
|
||||
{
|
||||
auto 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);
|
||||
|
||||
auto 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);
|
||||
|
||||
auto timeToThumbSampleFactor = rate / (double) sampsPerThumbSample;
|
||||
|
||||
startTime = cachedStart;
|
||||
auto sample = roundToInt (startTime * timeToThumbSampleFactor);
|
||||
|
||||
for (int i = numSamples; --i >= 0;)
|
||||
{
|
||||
auto 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)
|
||||
{
|
||||
auto 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)
|
||||
{
|
||||
}
|
||||
|
||||
AudioThumbnail::~AudioThumbnail()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
void AudioThumbnail::clear()
|
||||
{
|
||||
source.reset();
|
||||
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)
|
||||
{
|
||||
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
|
||||
|
||||
numSamplesFinished = 0;
|
||||
auto wasSuccessful = [&] { return sampleRate > 0 && totalSamples > 0; };
|
||||
|
||||
if (cache.loadThumb (*this, newSource->hashCode) && isFullyLoaded())
|
||||
{
|
||||
source.reset (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;
|
||||
|
||||
return wasSuccessful();
|
||||
}
|
||||
|
||||
source.reset (newSource);
|
||||
|
||||
const ScopedLock sl (lock);
|
||||
source->initialise (numSamplesFinished);
|
||||
|
||||
totalSamples = source->lengthInSamples;
|
||||
sampleRate = source->sampleRate;
|
||||
numChannels = (int32) source->numChannels;
|
||||
|
||||
createChannels (1 + (int) (totalSamples / samplesPerThumbSample));
|
||||
|
||||
return wasSuccessful();
|
||||
}
|
||||
|
||||
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 (int64 startSample, const AudioBuffer<float>& incoming,
|
||||
int startOffsetInBuffer, int numSamples)
|
||||
{
|
||||
jassert (startSample >= 0
|
||||
&& startOffsetInBuffer >= 0
|
||||
&& startOffsetInBuffer + numSamples <= incoming.getNumSamples());
|
||||
|
||||
auto firstThumbIndex = (int) (startSample / samplesPerThumbSample);
|
||||
auto lastThumbIndex = (int) ((startSample + numSamples + (samplesPerThumbSample - 1)) / samplesPerThumbSample);
|
||||
auto numToDo = lastThumbIndex - firstThumbIndex;
|
||||
|
||||
if (numToDo > 0)
|
||||
{
|
||||
auto numChans = jmin (channels.size(), incoming.getNumChannels());
|
||||
|
||||
const HeapBlock<MinMaxValue> thumbData (numToDo * numChans);
|
||||
const HeapBlock<MinMaxValue*> thumbChannels (numChans);
|
||||
|
||||
for (int chan = 0; chan < numChans; ++chan)
|
||||
{
|
||||
auto* sourceData = incoming.getReadPointer (chan, startOffsetInBuffer);
|
||||
auto* dest = thumbData + numToDo * chan;
|
||||
thumbChannels [chan] = dest;
|
||||
|
||||
for (int i = 0; i < numToDo; ++i)
|
||||
{
|
||||
auto 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);
|
||||
|
||||
auto start = thumbIndex * (int64) samplesPerThumbSample;
|
||||
auto end = (thumbIndex + numValues) * (int64) samplesPerThumbSample;
|
||||
|
||||
if (numSamplesFinished >= start && end > numSamplesFinished)
|
||||
numSamplesFinished = end;
|
||||
|
||||
totalSamples = jmax (numSamplesFinished, totalSamples.load());
|
||||
window->invalidate();
|
||||
sendChangeMessage();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
int AudioThumbnail::getNumChannels() const noexcept
|
||||
{
|
||||
return numChannels;
|
||||
}
|
||||
|
||||
double AudioThumbnail::getTotalLength() const noexcept
|
||||
{
|
||||
return sampleRate > 0 ? ((double) totalSamples / sampleRate) : 0.0;
|
||||
}
|
||||
|
||||
bool AudioThumbnail::isFullyLoaded() const noexcept
|
||||
{
|
||||
return numSamplesFinished >= totalSamples - samplesPerThumbSample;
|
||||
}
|
||||
|
||||
double AudioThumbnail::getProportionComplete() const noexcept
|
||||
{
|
||||
return jlimit (0.0, 1.0, (double) numSamplesFinished / (double) jmax ((int64) 1, totalSamples.load()));
|
||||
}
|
||||
|
||||
int64 AudioThumbnail::getNumSamplesFinished() const noexcept
|
||||
{
|
||||
return numSamplesFinished;
|
||||
}
|
||||
|
||||
float AudioThumbnail::getApproximatePeak() const
|
||||
{
|
||||
const ScopedLock sl (lock);
|
||||
int peak = 0;
|
||||
|
||||
for (auto* c : channels)
|
||||
peak = jmax (peak, c->getPeak());
|
||||
|
||||
return (float) jlimit (0, 127, peak) / 127.0f;
|
||||
}
|
||||
|
||||
void AudioThumbnail::getApproximateMinMax (double startTime, double endTime, int channelIndex,
|
||||
float& minValue, float& maxValue) const noexcept
|
||||
{
|
||||
const ScopedLock sl (lock);
|
||||
MinMaxValue result;
|
||||
auto* data = channels [channelIndex];
|
||||
|
||||
if (data != nullptr && sampleRate > 0)
|
||||
{
|
||||
auto firstThumbIndex = (int) ((startTime * sampleRate) / samplesPerThumbSample);
|
||||
auto 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.get(), channels);
|
||||
}
|
||||
|
||||
void AudioThumbnail::drawChannels (Graphics& g, const Rectangle<int>& area, double startTimeSeconds,
|
||||
double endTimeSeconds, float verticalZoomFactor)
|
||||
{
|
||||
for (int i = 0; i < numChannels; ++i)
|
||||
{
|
||||
auto y1 = roundToInt ((i * area.getHeight()) / numChannels);
|
||||
auto y2 = roundToInt (((i + 1) * area.getHeight()) / numChannels);
|
||||
|
||||
drawChannel (g, { area.getX(), area.getY() + y1, area.getWidth(), y2 - y1 },
|
||||
startTimeSeconds, endTimeSeconds, i, verticalZoomFactor);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace juce
|
222
deps/juce/modules/juce_audio_utils/gui/juce_AudioThumbnail.h
vendored
Normal file
222
deps/juce/modules/juce_audio_utils/gui/juce_AudioThumbnail.h
vendored
Normal file
@ -0,0 +1,222 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
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
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
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() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Clears and resets the thumbnail. */
|
||||
void clear() override;
|
||||
|
||||
/** 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 nullptr 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) override;
|
||||
|
||||
/** 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) override;
|
||||
|
||||
/** 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) override;
|
||||
|
||||
/** 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 AudioBuffer<float>& newData,
|
||||
int startOffsetInBuffer, int numSamples) override;
|
||||
|
||||
//==============================================================================
|
||||
/** 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) override;
|
||||
|
||||
/** 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 override;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the number of channels in the file. */
|
||||
int getNumChannels() const noexcept override;
|
||||
|
||||
/** Returns the length of the audio file, in seconds. */
|
||||
double getTotalLength() const noexcept override;
|
||||
|
||||
/** 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) override;
|
||||
|
||||
/** 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) override;
|
||||
|
||||
/** Returns true if the low res preview is fully generated. */
|
||||
bool isFullyLoaded() const noexcept override;
|
||||
|
||||
/** 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 override;
|
||||
|
||||
/** 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 override;
|
||||
|
||||
/** 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 override;
|
||||
|
||||
/** Returns the hash code that was set by setSource() or setReader(). */
|
||||
int64 getHashCode() const override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
AudioFormatManager& formatManagerToUse;
|
||||
AudioThumbnailCache& cache;
|
||||
|
||||
class LevelDataSource;
|
||||
struct MinMaxValue;
|
||||
class ThumbData;
|
||||
class CachedWindow;
|
||||
|
||||
std::unique_ptr<LevelDataSource> source;
|
||||
std::unique_ptr<CachedWindow> window;
|
||||
OwnedArray<ThumbData> channels;
|
||||
|
||||
int32 samplesPerThumbSample = 0;
|
||||
std::atomic<int64> totalSamples { 0 };
|
||||
int64 numSamplesFinished = 0;
|
||||
int32 numChannels = 0;
|
||||
double sampleRate = 0;
|
||||
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)
|
||||
};
|
||||
|
||||
} // namespace juce
|
158
deps/juce/modules/juce_audio_utils/gui/juce_AudioThumbnailBase.h
vendored
Normal file
158
deps/juce/modules/juce_audio_utils/gui/juce_AudioThumbnailBase.h
vendored
Normal file
@ -0,0 +1,158 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
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
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class JUCE_API AudioThumbnailBase : public ChangeBroadcaster,
|
||||
public AudioFormatWriter::ThreadedWriter::IncomingDataReceiver
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
AudioThumbnailBase() = default;
|
||||
~AudioThumbnailBase() override = default;
|
||||
|
||||
//==============================================================================
|
||||
/** 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 nullptr 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;
|
||||
};
|
||||
|
||||
} // namespace juce
|
197
deps/juce/modules/juce_audio_utils/gui/juce_AudioThumbnailCache.cpp
vendored
Normal file
197
deps/juce/modules/juce_audio_utils/gui/juce_AudioThumbnailCache.cpp
vendored
Normal file
@ -0,0 +1,197 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
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 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;
|
||||
}
|
||||
|
||||
} // namespace juce
|
118
deps/juce/modules/juce_audio_utils/gui/juce_AudioThumbnailCache.h
vendored
Normal file
118
deps/juce/modules/juce_audio_utils/gui/juce_AudioThumbnailCache.h
vendored
Normal file
@ -0,0 +1,118 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
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
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
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;
|
||||
OwnedArray<ThumbnailCacheEntry> thumbs;
|
||||
CriticalSection lock;
|
||||
int maxNumThumbsToStore;
|
||||
|
||||
ThumbnailCacheEntry* findThumbFor (int64 hash) const;
|
||||
int findOldestThumb() const;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioThumbnailCache)
|
||||
};
|
||||
|
||||
} // namespace juce
|
222
deps/juce/modules/juce_audio_utils/gui/juce_AudioVisualiserComponent.cpp
vendored
Normal file
222
deps/juce/modules/juce_audio_utils/gui/juce_AudioVisualiserComponent.cpp
vendored
Normal file
@ -0,0 +1,222 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
struct AudioVisualiserComponent::ChannelInfo
|
||||
{
|
||||
ChannelInfo (AudioVisualiserComponent& o, int bufferSize) : owner (o)
|
||||
{
|
||||
setBufferSize (bufferSize);
|
||||
clear();
|
||||
}
|
||||
|
||||
void clear() noexcept
|
||||
{
|
||||
levels.fill ({});
|
||||
value = {};
|
||||
subSample = 0;
|
||||
}
|
||||
|
||||
void pushSamples (const float* inputSamples, int num) noexcept
|
||||
{
|
||||
for (int i = 0; i < num; ++i)
|
||||
pushSample (inputSamples[i]);
|
||||
}
|
||||
|
||||
void pushSample (float newSample) noexcept
|
||||
{
|
||||
if (--subSample <= 0)
|
||||
{
|
||||
if (++nextSample == levels.size())
|
||||
nextSample = 0;
|
||||
|
||||
levels.getReference (nextSample) = value;
|
||||
subSample = owner.getSamplesPerBlock();
|
||||
value = Range<float> (newSample, newSample);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = value.getUnionWith (newSample);
|
||||
}
|
||||
}
|
||||
|
||||
void setBufferSize (int newSize)
|
||||
{
|
||||
levels.removeRange (newSize, levels.size());
|
||||
levels.insertMultiple (-1, {}, newSize - levels.size());
|
||||
|
||||
if (nextSample >= newSize)
|
||||
nextSample = 0;
|
||||
}
|
||||
|
||||
AudioVisualiserComponent& owner;
|
||||
Array<Range<float>> levels;
|
||||
Range<float> value;
|
||||
std::atomic<int> nextSample { 0 }, subSample { 0 };
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChannelInfo)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
AudioVisualiserComponent::AudioVisualiserComponent (int initialNumChannels)
|
||||
: numSamples (1024),
|
||||
inputSamplesPerBlock (256),
|
||||
backgroundColour (Colours::black),
|
||||
waveformColour (Colours::white)
|
||||
{
|
||||
setOpaque (true);
|
||||
setNumChannels (initialNumChannels);
|
||||
setRepaintRate (60);
|
||||
}
|
||||
|
||||
AudioVisualiserComponent::~AudioVisualiserComponent()
|
||||
{
|
||||
}
|
||||
|
||||
void AudioVisualiserComponent::setNumChannels (int numChannels)
|
||||
{
|
||||
channels.clear();
|
||||
|
||||
for (int i = 0; i < numChannels; ++i)
|
||||
channels.add (new ChannelInfo (*this, numSamples));
|
||||
}
|
||||
|
||||
void AudioVisualiserComponent::setBufferSize (int newNumSamples)
|
||||
{
|
||||
numSamples = newNumSamples;
|
||||
|
||||
for (auto* c : channels)
|
||||
c->setBufferSize (newNumSamples);
|
||||
}
|
||||
|
||||
void AudioVisualiserComponent::clear()
|
||||
{
|
||||
for (auto* c : channels)
|
||||
c->clear();
|
||||
}
|
||||
|
||||
void AudioVisualiserComponent::pushBuffer (const float** d, int numChannels, int num)
|
||||
{
|
||||
numChannels = jmin (numChannels, channels.size());
|
||||
|
||||
for (int i = 0; i < numChannels; ++i)
|
||||
channels.getUnchecked(i)->pushSamples (d[i], num);
|
||||
}
|
||||
|
||||
void AudioVisualiserComponent::pushBuffer (const AudioBuffer<float>& buffer)
|
||||
{
|
||||
pushBuffer (buffer.getArrayOfReadPointers(),
|
||||
buffer.getNumChannels(),
|
||||
buffer.getNumSamples());
|
||||
}
|
||||
|
||||
void AudioVisualiserComponent::pushBuffer (const AudioSourceChannelInfo& buffer)
|
||||
{
|
||||
auto numChannels = jmin (buffer.buffer->getNumChannels(), channels.size());
|
||||
|
||||
for (int i = 0; i < numChannels; ++i)
|
||||
channels.getUnchecked(i)->pushSamples (buffer.buffer->getReadPointer (i, buffer.startSample),
|
||||
buffer.numSamples);
|
||||
}
|
||||
|
||||
void AudioVisualiserComponent::pushSample (const float* d, int numChannels)
|
||||
{
|
||||
numChannels = jmin (numChannels, channels.size());
|
||||
|
||||
for (int i = 0; i < numChannels; ++i)
|
||||
channels.getUnchecked(i)->pushSample (d[i]);
|
||||
}
|
||||
|
||||
void AudioVisualiserComponent::setSamplesPerBlock (int newSamplesPerPixel) noexcept
|
||||
{
|
||||
inputSamplesPerBlock = newSamplesPerPixel;
|
||||
}
|
||||
|
||||
void AudioVisualiserComponent::setRepaintRate (int frequencyInHz)
|
||||
{
|
||||
startTimerHz (frequencyInHz);
|
||||
}
|
||||
|
||||
void AudioVisualiserComponent::timerCallback()
|
||||
{
|
||||
repaint();
|
||||
}
|
||||
|
||||
void AudioVisualiserComponent::setColours (Colour bk, Colour fg) noexcept
|
||||
{
|
||||
backgroundColour = bk;
|
||||
waveformColour = fg;
|
||||
repaint();
|
||||
}
|
||||
|
||||
void AudioVisualiserComponent::paint (Graphics& g)
|
||||
{
|
||||
g.fillAll (backgroundColour);
|
||||
|
||||
auto r = getLocalBounds().toFloat();
|
||||
auto channelHeight = r.getHeight() / (float) channels.size();
|
||||
|
||||
g.setColour (waveformColour);
|
||||
|
||||
for (auto* c : channels)
|
||||
paintChannel (g, r.removeFromTop (channelHeight),
|
||||
c->levels.begin(), c->levels.size(), c->nextSample);
|
||||
}
|
||||
|
||||
void AudioVisualiserComponent::getChannelAsPath (Path& path, const Range<float>* levels,
|
||||
int numLevels, int nextSample)
|
||||
{
|
||||
path.preallocateSpace (4 * numLevels + 8);
|
||||
|
||||
for (int i = 0; i < numLevels; ++i)
|
||||
{
|
||||
auto level = -(levels[(nextSample + i) % numLevels].getEnd());
|
||||
|
||||
if (i == 0)
|
||||
path.startNewSubPath (0.0f, level);
|
||||
else
|
||||
path.lineTo ((float) i, level);
|
||||
}
|
||||
|
||||
for (int i = numLevels; --i >= 0;)
|
||||
path.lineTo ((float) i, -(levels[(nextSample + i) % numLevels].getStart()));
|
||||
|
||||
path.closeSubPath();
|
||||
}
|
||||
|
||||
void AudioVisualiserComponent::paintChannel (Graphics& g, Rectangle<float> area,
|
||||
const Range<float>* levels, int numLevels, int nextSample)
|
||||
{
|
||||
Path p;
|
||||
getChannelAsPath (p, levels, numLevels, nextSample);
|
||||
|
||||
g.fillPath (p, AffineTransform::fromTargetPoints (0.0f, -1.0f, area.getX(), area.getY(),
|
||||
0.0f, 1.0f, area.getX(), area.getBottom(),
|
||||
(float) numLevels, -1.0f, area.getRight(), area.getY()));
|
||||
}
|
||||
|
||||
} // namespace juce
|
133
deps/juce/modules/juce_audio_utils/gui/juce_AudioVisualiserComponent.h
vendored
Normal file
133
deps/juce/modules/juce_audio_utils/gui/juce_AudioVisualiserComponent.h
vendored
Normal file
@ -0,0 +1,133 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A simple component that can be used to show a scrolling waveform of audio data.
|
||||
|
||||
This is a handy way to get a quick visualisation of some audio data. Just create
|
||||
one of these, set its size and oversampling rate, and then feed it with incoming
|
||||
data by calling one of its pushBuffer() or pushSample() methods.
|
||||
|
||||
You can override its paint method for more customised views, but it's only designed
|
||||
as a quick-and-dirty class for simple tasks, so please don't send us feature requests
|
||||
for fancy additional features that you'd like it to support! If you're building a
|
||||
real-world app that requires more powerful waveform display, you'll probably want to
|
||||
create your own component instead.
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class JUCE_API AudioVisualiserComponent : public Component,
|
||||
private Timer
|
||||
{
|
||||
public:
|
||||
/** Creates a visualiser with the given number of channels. */
|
||||
AudioVisualiserComponent (int initialNumChannels);
|
||||
|
||||
/** Destructor. */
|
||||
~AudioVisualiserComponent() override;
|
||||
|
||||
/** Changes the number of channels that the visualiser stores. */
|
||||
void setNumChannels (int numChannels);
|
||||
|
||||
/** Changes the number of samples that the visualiser keeps in its history.
|
||||
Note that this value refers to the number of averaged sample blocks, and each
|
||||
block is calculated as the peak of a number of incoming audio samples. To set
|
||||
the number of incoming samples per block, use setSamplesPerBlock().
|
||||
*/
|
||||
void setBufferSize (int bufferSize);
|
||||
|
||||
/** */
|
||||
void setSamplesPerBlock (int newNumInputSamplesPerBlock) noexcept;
|
||||
|
||||
/** */
|
||||
int getSamplesPerBlock() const noexcept { return inputSamplesPerBlock; }
|
||||
|
||||
/** Clears the contents of the buffers. */
|
||||
void clear();
|
||||
|
||||
/** Pushes a buffer of channels data.
|
||||
The number of channels provided here is expected to match the number of channels
|
||||
that this AudioVisualiserComponent has been told to use.
|
||||
*/
|
||||
void pushBuffer (const AudioBuffer<float>& bufferToPush);
|
||||
|
||||
/** Pushes a buffer of channels data.
|
||||
The number of channels provided here is expected to match the number of channels
|
||||
that this AudioVisualiserComponent has been told to use.
|
||||
*/
|
||||
void pushBuffer (const AudioSourceChannelInfo& bufferToPush);
|
||||
|
||||
/** Pushes a buffer of channels data.
|
||||
The number of channels provided here is expected to match the number of channels
|
||||
that this AudioVisualiserComponent has been told to use.
|
||||
*/
|
||||
void pushBuffer (const float** channelData, int numChannels, int numSamples);
|
||||
|
||||
/** Pushes a single sample (per channel).
|
||||
The number of channels provided here is expected to match the number of channels
|
||||
that this AudioVisualiserComponent has been told to use.
|
||||
*/
|
||||
void pushSample (const float* samplesForEachChannel, int numChannels);
|
||||
|
||||
/** Sets the colours used to paint the */
|
||||
void setColours (Colour backgroundColour, Colour waveformColour) noexcept;
|
||||
|
||||
/** Sets the frequency at which the component repaints itself. */
|
||||
void setRepaintRate (int frequencyInHz);
|
||||
|
||||
/** Draws a channel of audio data in the given bounds.
|
||||
The default implementation just calls getChannelAsPath() and fits this into the given
|
||||
area. You may want to override this to draw things differently.
|
||||
*/
|
||||
virtual void paintChannel (Graphics&, Rectangle<float> bounds,
|
||||
const Range<float>* levels, int numLevels, int nextSample);
|
||||
|
||||
/** Creates a path which contains the waveform shape of a given set of range data.
|
||||
The path is normalised so that -1 and +1 are its upper and lower bounds, and it
|
||||
goes from 0 to numLevels on the X axis.
|
||||
*/
|
||||
void getChannelAsPath (Path& result, const Range<float>* levels, int numLevels, int nextSample);
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
|
||||
private:
|
||||
struct ChannelInfo;
|
||||
|
||||
OwnedArray<ChannelInfo> channels;
|
||||
int numSamples, inputSamplesPerBlock;
|
||||
Colour backgroundColour, waveformColour;
|
||||
|
||||
void timerCallback() override;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioVisualiserComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
83
deps/juce/modules/juce_audio_utils/gui/juce_BluetoothMidiDevicePairingDialogue.h
vendored
Normal file
83
deps/juce/modules/juce_audio_utils/gui/juce_BluetoothMidiDevicePairingDialogue.h
vendored
Normal file
@ -0,0 +1,83 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Opens a Bluetooth MIDI pairing dialogue that allows the user to view and
|
||||
connect to Bluetooth MIDI devices that are currently found nearby.
|
||||
|
||||
The dialogue will ignore non-MIDI Bluetooth devices.
|
||||
|
||||
Only after a Bluetooth MIDI device has been paired will its MIDI ports
|
||||
be available through JUCE's MidiInput and MidiOutput classes.
|
||||
|
||||
This dialogue is currently only available on macOS targetting versions 10.11+,
|
||||
iOS and Android. When targeting older versions of macOS you should instead
|
||||
pair Bluetooth MIDI devices using the "Audio MIDI Setup" app (located in
|
||||
/Applications/Utilities). On Windows, you should use the system settings. On
|
||||
Linux, Bluetooth MIDI devices are currently not supported.
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class JUCE_API BluetoothMidiDevicePairingDialogue
|
||||
{
|
||||
public:
|
||||
|
||||
/** Opens the Bluetooth MIDI pairing dialogue, if it is available.
|
||||
|
||||
@param exitCallback A callback which will be called when the modal
|
||||
bluetooth dialog is closed.
|
||||
@param btWindowBounds The bounds of the bluetooth window that will
|
||||
be opened. The dialog itself is opened by the OS so cannot
|
||||
be customised by JUCE.
|
||||
@return true if the dialogue was opened, false on error.
|
||||
|
||||
@see ModalComponentManager::Callback
|
||||
*/
|
||||
static bool open (ModalComponentManager::Callback* exitCallback = nullptr,
|
||||
Rectangle<int>* btWindowBounds = nullptr);
|
||||
|
||||
/** Checks if a Bluetooth MIDI pairing dialogue is available on this
|
||||
platform.
|
||||
|
||||
On iOS, this will be true for iOS versions 8.0 and higher.
|
||||
|
||||
On Android, this will be true only for Android SDK versions 23 and
|
||||
higher, and additionally only if the device itself supports MIDI
|
||||
over Bluetooth.
|
||||
|
||||
On desktop platforms, this will typically be false as the bluetooth
|
||||
pairing is not done inside the app but by other means.
|
||||
|
||||
@return true if the Bluetooth MIDI pairing dialogue is available,
|
||||
false otherwise.
|
||||
*/
|
||||
static bool isAvailable();
|
||||
};
|
||||
|
||||
} // namespace juce
|
912
deps/juce/modules/juce_audio_utils/gui/juce_MidiKeyboardComponent.cpp
vendored
Normal file
912
deps/juce/modules/juce_audio_utils/gui/juce_MidiKeyboardComponent.cpp
vendored
Normal file
@ -0,0 +1,912 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
static const uint8 whiteNotes[] = { 0, 2, 4, 5, 7, 9, 11 };
|
||||
static const uint8 blackNotes[] = { 1, 3, 6, 8, 10 };
|
||||
|
||||
|
||||
struct MidiKeyboardComponent::UpDownButton : public Button
|
||||
{
|
||||
UpDownButton (MidiKeyboardComponent& c, int d)
|
||||
: Button ({}), owner (c), delta (d)
|
||||
{
|
||||
}
|
||||
|
||||
void clicked() override
|
||||
{
|
||||
auto note = owner.getLowestVisibleKey();
|
||||
|
||||
if (delta < 0)
|
||||
note = (note - 1) / 12;
|
||||
else
|
||||
note = note / 12 + 1;
|
||||
|
||||
owner.setLowestVisibleKey (note * 12);
|
||||
}
|
||||
|
||||
using Button::clicked;
|
||||
|
||||
void paintButton (Graphics& g, bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) override
|
||||
{
|
||||
owner.drawUpDownButton (g, getWidth(), getHeight(),
|
||||
shouldDrawButtonAsHighlighted, shouldDrawButtonAsDown,
|
||||
delta > 0);
|
||||
}
|
||||
|
||||
private:
|
||||
MidiKeyboardComponent& owner;
|
||||
const int delta;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (UpDownButton)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
MidiKeyboardComponent::MidiKeyboardComponent (MidiKeyboardState& s, Orientation o)
|
||||
: state (s), orientation (o)
|
||||
{
|
||||
scrollDown.reset (new UpDownButton (*this, -1));
|
||||
scrollUp .reset (new UpDownButton (*this, 1));
|
||||
|
||||
addChildComponent (scrollDown.get());
|
||||
addChildComponent (scrollUp.get());
|
||||
|
||||
// initialise with a default set of qwerty key-mappings..
|
||||
int note = 0;
|
||||
|
||||
for (char c : "awsedftgyhujkolp;")
|
||||
setKeyPressForNote (KeyPress (c, 0, 0), note++);
|
||||
|
||||
mouseOverNotes.insertMultiple (0, -1, 32);
|
||||
mouseDownNotes.insertMultiple (0, -1, 32);
|
||||
|
||||
colourChanged();
|
||||
setWantsKeyboardFocus (true);
|
||||
|
||||
state.addListener (this);
|
||||
|
||||
startTimerHz (20);
|
||||
}
|
||||
|
||||
MidiKeyboardComponent::~MidiKeyboardComponent()
|
||||
{
|
||||
state.removeListener (this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void MidiKeyboardComponent::setKeyWidth (float widthInPixels)
|
||||
{
|
||||
jassert (widthInPixels > 0);
|
||||
|
||||
if (keyWidth != widthInPixels) // Prevent infinite recursion if the width is being computed in a 'resized()' call-back
|
||||
{
|
||||
keyWidth = widthInPixels;
|
||||
resized();
|
||||
}
|
||||
}
|
||||
|
||||
void MidiKeyboardComponent::setScrollButtonWidth (int widthInPixels)
|
||||
{
|
||||
jassert (widthInPixels > 0);
|
||||
|
||||
if (scrollButtonWidth != widthInPixels)
|
||||
{
|
||||
scrollButtonWidth = widthInPixels;
|
||||
resized();
|
||||
}
|
||||
}
|
||||
|
||||
void MidiKeyboardComponent::setOrientation (Orientation newOrientation)
|
||||
{
|
||||
if (orientation != newOrientation)
|
||||
{
|
||||
orientation = newOrientation;
|
||||
resized();
|
||||
}
|
||||
}
|
||||
|
||||
void MidiKeyboardComponent::setAvailableRange (int lowestNote, 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)
|
||||
{
|
||||
bool hasMoved = (((int) firstKey) != (int) noteNumber);
|
||||
firstKey = noteNumber;
|
||||
|
||||
if (hasMoved)
|
||||
sendChangeMessage();
|
||||
|
||||
resized();
|
||||
}
|
||||
}
|
||||
|
||||
void MidiKeyboardComponent::setScrollButtonsVisible (bool newCanScroll)
|
||||
{
|
||||
if (canScroll != newCanScroll)
|
||||
{
|
||||
canScroll = newCanScroll;
|
||||
resized();
|
||||
}
|
||||
}
|
||||
|
||||
void MidiKeyboardComponent::setScrollButtonsWidth (int width)
|
||||
{
|
||||
if (scrollButtonWidth != width) {
|
||||
scrollButtonWidth = width;
|
||||
resized();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void MidiKeyboardComponent::colourChanged()
|
||||
{
|
||||
setOpaque (findColour (whiteNoteColourId).isOpaque());
|
||||
repaint();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void MidiKeyboardComponent::setMidiChannel (int midiChannelNumber)
|
||||
{
|
||||
jassert (midiChannelNumber > 0 && midiChannelNumber <= 16);
|
||||
|
||||
if (midiChannel != midiChannelNumber)
|
||||
{
|
||||
resetAnyKeysInUse();
|
||||
midiChannel = jlimit (1, 16, midiChannelNumber);
|
||||
}
|
||||
}
|
||||
|
||||
void MidiKeyboardComponent::setMidiChannelsToDisplay (int midiChannelMask)
|
||||
{
|
||||
midiInChannelMask = midiChannelMask;
|
||||
noPendingUpdates.store (false);
|
||||
}
|
||||
|
||||
void MidiKeyboardComponent::setVelocity (float v, bool useMousePosition)
|
||||
{
|
||||
velocity = jlimit (0.0f, 1.0f, v);
|
||||
useMousePositionForVelocity = useMousePosition;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Range<float> MidiKeyboardComponent::getKeyPosition (int midiNoteNumber, float targetKeyWidth) const
|
||||
{
|
||||
jassert (midiNoteNumber >= 0 && midiNoteNumber < 128);
|
||||
|
||||
static const float notePos[] = { 0.0f, 1 - blackNoteWidthRatio * 0.6f,
|
||||
1.0f, 2 - blackNoteWidthRatio * 0.4f,
|
||||
2.0f,
|
||||
3.0f, 4 - blackNoteWidthRatio * 0.7f,
|
||||
4.0f, 5 - blackNoteWidthRatio * 0.5f,
|
||||
5.0f, 6 - blackNoteWidthRatio * 0.3f,
|
||||
6.0f };
|
||||
|
||||
auto octave = midiNoteNumber / 12;
|
||||
auto note = midiNoteNumber % 12;
|
||||
|
||||
auto start = (float) octave * 7.0f * targetKeyWidth + notePos[note] * targetKeyWidth;
|
||||
auto width = MidiMessage::isMidiNoteBlack (note) ? blackNoteWidthRatio * targetKeyWidth : targetKeyWidth;
|
||||
|
||||
return { start, start + width };
|
||||
}
|
||||
|
||||
Range<float> MidiKeyboardComponent::getKeyPos (int midiNoteNumber) const
|
||||
{
|
||||
return getKeyPosition (midiNoteNumber, keyWidth)
|
||||
- xOffset
|
||||
- getKeyPosition (rangeStart, keyWidth).getStart();
|
||||
}
|
||||
|
||||
Rectangle<float> MidiKeyboardComponent::getRectangleForKey (int note) const
|
||||
{
|
||||
jassert (note >= rangeStart && note <= rangeEnd);
|
||||
|
||||
auto pos = getKeyPos (note);
|
||||
auto x = pos.getStart();
|
||||
auto w = pos.getLength();
|
||||
|
||||
if (MidiMessage::isMidiNoteBlack (note))
|
||||
{
|
||||
auto blackNoteLength = getBlackNoteLength();
|
||||
|
||||
switch (orientation)
|
||||
{
|
||||
case horizontalKeyboard: return { x, 0, w, blackNoteLength };
|
||||
case verticalKeyboardFacingLeft: return { (float) getWidth() - blackNoteLength, x, blackNoteLength, w };
|
||||
case verticalKeyboardFacingRight: return { 0, (float) getHeight() - x - w, blackNoteLength, w };
|
||||
default: jassertfalse; break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (orientation)
|
||||
{
|
||||
case horizontalKeyboard: return { x, 0, w, (float) getHeight() };
|
||||
case verticalKeyboardFacingLeft: return { 0, x, (float) getWidth(), w };
|
||||
case verticalKeyboardFacingRight: return { 0, (float) getHeight() - x - w, (float) getWidth(), w };
|
||||
default: jassertfalse; break;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
float MidiKeyboardComponent::getKeyStartPosition (int midiNoteNumber) const
|
||||
{
|
||||
return getKeyPos (midiNoteNumber).getStart();
|
||||
}
|
||||
|
||||
float MidiKeyboardComponent::getTotalKeyboardWidth() const noexcept
|
||||
{
|
||||
return getKeyPos (rangeEnd).getEnd();
|
||||
}
|
||||
|
||||
int MidiKeyboardComponent::getNoteAtPosition (Point<float> p)
|
||||
{
|
||||
float v;
|
||||
return xyToNote (p, v);
|
||||
}
|
||||
|
||||
int MidiKeyboardComponent::xyToNote (Point<float> pos, float& mousePositionVelocity)
|
||||
{
|
||||
if (! reallyContains (pos.toInt(), false))
|
||||
return -1;
|
||||
|
||||
auto p = pos;
|
||||
|
||||
if (orientation != horizontalKeyboard)
|
||||
{
|
||||
p = { p.y, p.x };
|
||||
|
||||
if (orientation == verticalKeyboardFacingLeft)
|
||||
p = { p.x, (float) getWidth() - p.y };
|
||||
else
|
||||
p = { (float) getHeight() - p.x, p.y };
|
||||
}
|
||||
|
||||
return remappedXYToNote (p + Point<float> (xOffset, 0), mousePositionVelocity);
|
||||
}
|
||||
|
||||
int MidiKeyboardComponent::remappedXYToNote (Point<float> pos, float& mousePositionVelocity) const
|
||||
{
|
||||
auto blackNoteLength = getBlackNoteLength();
|
||||
|
||||
if (pos.getY() < blackNoteLength)
|
||||
{
|
||||
for (int octaveStart = 12 * (rangeStart / 12); octaveStart <= rangeEnd; octaveStart += 12)
|
||||
{
|
||||
for (int i = 0; i < 5; ++i)
|
||||
{
|
||||
auto note = octaveStart + blackNotes[i];
|
||||
|
||||
if (note >= rangeStart && note <= rangeEnd)
|
||||
{
|
||||
if (getKeyPos (note).contains (pos.x - xOffset))
|
||||
{
|
||||
mousePositionVelocity = jmax (0.0f, pos.y / blackNoteLength);
|
||||
return note;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int octaveStart = 12 * (rangeStart / 12); octaveStart <= rangeEnd; octaveStart += 12)
|
||||
{
|
||||
for (int i = 0; i < 7; ++i)
|
||||
{
|
||||
auto note = octaveStart + whiteNotes[i];
|
||||
|
||||
if (note >= rangeStart && note <= rangeEnd)
|
||||
{
|
||||
if (getKeyPos (note).contains (pos.x - xOffset))
|
||||
{
|
||||
auto whiteNoteLength = (orientation == horizontalKeyboard) ? getHeight() : getWidth();
|
||||
mousePositionVelocity = jmax (0.0f, pos.y / (float) whiteNoteLength);
|
||||
return note;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mousePositionVelocity = 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void MidiKeyboardComponent::repaintNote (int noteNum)
|
||||
{
|
||||
if (noteNum >= rangeStart && noteNum <= rangeEnd)
|
||||
repaint (getRectangleForKey (noteNum).getSmallestIntegerContainer());
|
||||
}
|
||||
|
||||
void MidiKeyboardComponent::paint (Graphics& g)
|
||||
{
|
||||
g.fillAll (findColour (whiteNoteColourId));
|
||||
|
||||
auto lineColour = findColour (keySeparatorLineColourId);
|
||||
auto textColour = findColour (textLabelColourId);
|
||||
|
||||
for (int octave = 0; octave < 128; octave += 12)
|
||||
{
|
||||
for (int white = 0; white < 7; ++white)
|
||||
{
|
||||
auto noteNum = octave + whiteNotes[white];
|
||||
|
||||
if (noteNum >= rangeStart && noteNum <= rangeEnd)
|
||||
drawWhiteNote (noteNum, g, getRectangleForKey (noteNum),
|
||||
state.isNoteOnForChannels (midiInChannelMask, noteNum),
|
||||
mouseOverNotes.contains (noteNum), lineColour, textColour);
|
||||
}
|
||||
}
|
||||
|
||||
float x1 = 0.0f, y1 = 0.0f, x2 = 0.0f, y2 = 0.0f;
|
||||
auto width = getWidth();
|
||||
auto height = getHeight();
|
||||
|
||||
if (orientation == verticalKeyboardFacingLeft)
|
||||
{
|
||||
x1 = (float) width - 1.0f;
|
||||
x2 = (float) width - 5.0f;
|
||||
}
|
||||
else if (orientation == verticalKeyboardFacingRight)
|
||||
x2 = 5.0f;
|
||||
else
|
||||
y2 = 5.0f;
|
||||
|
||||
auto x = getKeyPos (rangeEnd).getEnd();
|
||||
auto shadowCol = findColour (shadowColourId);
|
||||
|
||||
if (! shadowCol.isTransparent())
|
||||
{
|
||||
g.setGradientFill (ColourGradient (shadowCol, x1, y1, shadowCol.withAlpha (0.0f), x2, y2, false));
|
||||
|
||||
switch (orientation)
|
||||
{
|
||||
case horizontalKeyboard: g.fillRect (0.0f, 0.0f, x, 5.0f); break;
|
||||
case verticalKeyboardFacingLeft: g.fillRect ((float) width - 5.0f, 0.0f, 5.0f, x); break;
|
||||
case verticalKeyboardFacingRight: g.fillRect (0.0f, 0.0f, 5.0f, x); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
if (! lineColour.isTransparent())
|
||||
{
|
||||
g.setColour (lineColour);
|
||||
|
||||
switch (orientation)
|
||||
{
|
||||
case horizontalKeyboard: g.fillRect (0.0f, (float) height - 1.0f, x, 1.0f); break;
|
||||
case verticalKeyboardFacingLeft: g.fillRect (0.0f, 0.0f, 1.0f, x); break;
|
||||
case verticalKeyboardFacingRight: g.fillRect ((float) width - 1.0f, 0.0f, 1.0f, x); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
auto blackNoteColour = findColour (blackNoteColourId);
|
||||
|
||||
for (int octave = 0; octave < 128; octave += 12)
|
||||
{
|
||||
for (int black = 0; black < 5; ++black)
|
||||
{
|
||||
auto noteNum = octave + blackNotes[black];
|
||||
|
||||
if (noteNum >= rangeStart && noteNum <= rangeEnd)
|
||||
drawBlackNote (noteNum, g, getRectangleForKey (noteNum),
|
||||
state.isNoteOnForChannels (midiInChannelMask, noteNum),
|
||||
mouseOverNotes.contains (noteNum), blackNoteColour);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MidiKeyboardComponent::drawWhiteNote (int midiNoteNumber, Graphics& g, Rectangle<float> area,
|
||||
bool isDown, bool isOver, Colour lineColour, Colour textColour)
|
||||
{
|
||||
auto c = Colours::transparentWhite;
|
||||
|
||||
if (isDown) c = findColour (keyDownOverlayColourId);
|
||||
if (isOver) c = c.overlaidWith (findColour (mouseOverKeyOverlayColourId));
|
||||
|
||||
g.setColour (c);
|
||||
g.fillRect (area);
|
||||
|
||||
auto text = getWhiteNoteText (midiNoteNumber);
|
||||
|
||||
if (text.isNotEmpty())
|
||||
{
|
||||
auto fontHeight = jmin (12.0f, keyWidth * 0.9f);
|
||||
|
||||
g.setColour (textColour);
|
||||
g.setFont (Font (fontHeight).withHorizontalScale (0.8f));
|
||||
|
||||
switch (orientation)
|
||||
{
|
||||
case horizontalKeyboard: g.drawText (text, area.withTrimmedLeft (1.0f).withTrimmedBottom (2.0f), Justification::centredBottom, false); break;
|
||||
case verticalKeyboardFacingLeft: g.drawText (text, area.reduced (2.0f), Justification::centredLeft, false); break;
|
||||
case verticalKeyboardFacingRight: g.drawText (text, area.reduced (2.0f), Justification::centredRight, false); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
if (! lineColour.isTransparent())
|
||||
{
|
||||
g.setColour (lineColour);
|
||||
|
||||
switch (orientation)
|
||||
{
|
||||
case horizontalKeyboard: g.fillRect (area.withWidth (1.0f)); break;
|
||||
case verticalKeyboardFacingLeft: g.fillRect (area.withHeight (1.0f)); break;
|
||||
case verticalKeyboardFacingRight: g.fillRect (area.removeFromBottom (1.0f)); break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (midiNoteNumber == rangeEnd)
|
||||
{
|
||||
switch (orientation)
|
||||
{
|
||||
case horizontalKeyboard: g.fillRect (area.expanded (1.0f, 0).removeFromRight (1.0f)); break;
|
||||
case verticalKeyboardFacingLeft: g.fillRect (area.expanded (0, 1.0f).removeFromBottom (1.0f)); break;
|
||||
case verticalKeyboardFacingRight: g.fillRect (area.expanded (0, 1.0f).removeFromTop (1.0f)); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MidiKeyboardComponent::drawBlackNote (int /*midiNoteNumber*/, Graphics& g, Rectangle<float> area,
|
||||
bool isDown, bool isOver, Colour noteFillColour)
|
||||
{
|
||||
auto c = noteFillColour;
|
||||
|
||||
if (isDown) c = c.overlaidWith (findColour (keyDownOverlayColourId));
|
||||
if (isOver) c = c.overlaidWith (findColour (mouseOverKeyOverlayColourId));
|
||||
|
||||
g.setColour (c);
|
||||
g.fillRect (area);
|
||||
|
||||
if (isDown)
|
||||
{
|
||||
g.setColour (noteFillColour);
|
||||
g.drawRect (area);
|
||||
}
|
||||
else
|
||||
{
|
||||
g.setColour (c.brighter());
|
||||
auto sideIndent = 1.0f / 8.0f;
|
||||
auto topIndent = 7.0f / 8.0f;
|
||||
auto w = area.getWidth();
|
||||
auto h = area.getHeight();
|
||||
|
||||
switch (orientation)
|
||||
{
|
||||
case horizontalKeyboard: g.fillRect (area.reduced (w * sideIndent, 0).removeFromTop (h * topIndent)); break;
|
||||
case verticalKeyboardFacingLeft: g.fillRect (area.reduced (0, h * sideIndent).removeFromRight (w * topIndent)); break;
|
||||
case verticalKeyboardFacingRight: g.fillRect (area.reduced (0, h * sideIndent).removeFromLeft (w * topIndent)); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MidiKeyboardComponent::setOctaveForMiddleC (int octaveNum)
|
||||
{
|
||||
octaveNumForMiddleC = octaveNum;
|
||||
repaint();
|
||||
}
|
||||
|
||||
String MidiKeyboardComponent::getWhiteNoteText (int midiNoteNumber)
|
||||
{
|
||||
if (midiNoteNumber % 12 == 0)
|
||||
return MidiMessage::getMidiNoteName (midiNoteNumber, true, true, octaveNumForMiddleC);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void MidiKeyboardComponent::drawUpDownButton (Graphics& g, int w, int h,
|
||||
bool mouseOver,
|
||||
bool buttonDown,
|
||||
bool movesOctavesUp)
|
||||
{
|
||||
g.fillAll (findColour (upDownButtonBackgroundColourId));
|
||||
|
||||
float angle = 0;
|
||||
|
||||
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; break;
|
||||
}
|
||||
|
||||
Path path;
|
||||
path.addTriangle (0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.5f);
|
||||
path.applyTransform (AffineTransform::rotation (MathConstants<float>::twoPi * 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, (float) w - 2.0f, (float) h - 2.0f, true));
|
||||
}
|
||||
|
||||
void MidiKeyboardComponent::setBlackNoteLengthProportion (float ratio) noexcept
|
||||
{
|
||||
jassert (ratio >= 0.0f && ratio <= 1.0f);
|
||||
|
||||
if (blackNoteLengthRatio != ratio)
|
||||
{
|
||||
blackNoteLengthRatio = ratio;
|
||||
resized();
|
||||
}
|
||||
}
|
||||
|
||||
float MidiKeyboardComponent::getBlackNoteLength() const noexcept
|
||||
{
|
||||
auto whiteNoteLength = orientation == horizontalKeyboard ? getHeight() : getWidth();
|
||||
return (float) whiteNoteLength * blackNoteLengthRatio;
|
||||
}
|
||||
|
||||
void MidiKeyboardComponent::setBlackNoteWidthProportion (float ratio) noexcept
|
||||
{
|
||||
jassert (ratio >= 0.0f && ratio <= 1.0f);
|
||||
|
||||
if (blackNoteWidthRatio != ratio)
|
||||
{
|
||||
blackNoteWidthRatio = ratio;
|
||||
resized();
|
||||
}
|
||||
}
|
||||
|
||||
void MidiKeyboardComponent::resized()
|
||||
{
|
||||
auto w = getWidth();
|
||||
auto h = getHeight();
|
||||
|
||||
if (w > 0 && h > 0)
|
||||
{
|
||||
if (orientation != horizontalKeyboard)
|
||||
std::swap (w, h);
|
||||
|
||||
auto kx2 = getKeyPos (rangeEnd).getEnd();
|
||||
|
||||
if ((int) firstKey != rangeStart)
|
||||
{
|
||||
auto kx1 = getKeyPos (rangeStart).getStart();
|
||||
|
||||
if (kx2 - kx1 <= (float) w)
|
||||
{
|
||||
firstKey = (float) rangeStart;
|
||||
sendChangeMessage();
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
scrollDown->setVisible (canScroll && firstKey > (float) rangeStart);
|
||||
|
||||
xOffset = 0;
|
||||
|
||||
if (canScroll)
|
||||
{
|
||||
auto scrollButtonW = jmin (scrollButtonWidth, w / 2);
|
||||
auto 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));
|
||||
}
|
||||
|
||||
auto endOfLastKey = getKeyPos (rangeEnd).getEnd();
|
||||
|
||||
float mousePositionVelocity;
|
||||
auto spaceAvailable = w;
|
||||
auto lastStartKey = remappedXYToNote ({ endOfLastKey - (float) spaceAvailable, 0 }, mousePositionVelocity) + 1;
|
||||
|
||||
if (lastStartKey >= 0 && ((int) firstKey) > lastStartKey)
|
||||
{
|
||||
firstKey = (float) jlimit (rangeStart, rangeEnd, lastStartKey);
|
||||
sendChangeMessage();
|
||||
}
|
||||
|
||||
xOffset = getKeyPos ((int) firstKey).getStart();
|
||||
}
|
||||
else
|
||||
{
|
||||
firstKey = (float) rangeStart;
|
||||
}
|
||||
|
||||
scrollUp->setVisible (canScroll && getKeyPos (rangeEnd).getStart() > (float) w);
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void MidiKeyboardComponent::handleNoteOn (MidiKeyboardState*, int /*midiChannel*/, int /*midiNoteNumber*/, float /*velocity*/)
|
||||
{
|
||||
noPendingUpdates.store (false);
|
||||
}
|
||||
|
||||
void MidiKeyboardComponent::handleNoteOff (MidiKeyboardState*, int /*midiChannel*/, int /*midiNoteNumber*/, float /*velocity*/)
|
||||
{
|
||||
noPendingUpdates.store (false);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void MidiKeyboardComponent::resetAnyKeysInUse()
|
||||
{
|
||||
if (! keysPressed.isZero())
|
||||
{
|
||||
for (int i = 128; --i >= 0;)
|
||||
if (keysPressed[i])
|
||||
state.noteOff (midiChannel, i, 0.0f);
|
||||
|
||||
keysPressed.clear();
|
||||
}
|
||||
|
||||
for (int i = mouseDownNotes.size(); --i >= 0;)
|
||||
{
|
||||
auto noteDown = mouseDownNotes.getUnchecked(i);
|
||||
|
||||
if (noteDown >= 0)
|
||||
{
|
||||
state.noteOff (midiChannel, noteDown, 0.0f);
|
||||
mouseDownNotes.set (i, -1);
|
||||
}
|
||||
|
||||
mouseOverNotes.set (i, -1);
|
||||
}
|
||||
}
|
||||
|
||||
void MidiKeyboardComponent::updateNoteUnderMouse (const MouseEvent& e, bool isDown)
|
||||
{
|
||||
updateNoteUnderMouse (e.getEventRelativeTo (this).position, isDown, e.source.getIndex());
|
||||
}
|
||||
|
||||
void MidiKeyboardComponent::updateNoteUnderMouse (Point<float> pos, bool isDown, int fingerNum)
|
||||
{
|
||||
float mousePositionVelocity = 0.0f;
|
||||
auto newNote = xyToNote (pos, mousePositionVelocity);
|
||||
auto oldNote = mouseOverNotes.getUnchecked (fingerNum);
|
||||
auto oldNoteDown = mouseDownNotes.getUnchecked (fingerNum);
|
||||
auto eventVelocity = useMousePositionForVelocity ? mousePositionVelocity * velocity : velocity;
|
||||
|
||||
if (oldNote != newNote)
|
||||
{
|
||||
repaintNote (oldNote);
|
||||
repaintNote (newNote);
|
||||
mouseOverNotes.set (fingerNum, newNote);
|
||||
}
|
||||
|
||||
if (isDown)
|
||||
{
|
||||
if (newNote != oldNoteDown)
|
||||
{
|
||||
if (oldNoteDown >= 0)
|
||||
{
|
||||
mouseDownNotes.set (fingerNum, -1);
|
||||
|
||||
if (! mouseDownNotes.contains (oldNoteDown))
|
||||
state.noteOff (midiChannel, oldNoteDown, eventVelocity);
|
||||
}
|
||||
|
||||
if (newNote >= 0 && ! mouseDownNotes.contains (newNote))
|
||||
{
|
||||
state.noteOn (midiChannel, newNote, eventVelocity);
|
||||
mouseDownNotes.set (fingerNum, newNote);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (oldNoteDown >= 0)
|
||||
{
|
||||
mouseDownNotes.set (fingerNum, -1);
|
||||
|
||||
if (! mouseDownNotes.contains (oldNoteDown))
|
||||
state.noteOff (midiChannel, oldNoteDown, eventVelocity);
|
||||
}
|
||||
}
|
||||
|
||||
void MidiKeyboardComponent::mouseMove (const MouseEvent& e)
|
||||
{
|
||||
updateNoteUnderMouse (e, false);
|
||||
}
|
||||
|
||||
void MidiKeyboardComponent::mouseDrag (const MouseEvent& e)
|
||||
{
|
||||
float mousePositionVelocity;
|
||||
auto newNote = xyToNote (e.position, mousePositionVelocity);
|
||||
|
||||
if (newNote >= 0 && mouseDraggedToKey (newNote, e))
|
||||
updateNoteUnderMouse (e, true);
|
||||
}
|
||||
|
||||
bool MidiKeyboardComponent::mouseDownOnKey (int, const MouseEvent&) { return true; }
|
||||
bool MidiKeyboardComponent::mouseDraggedToKey (int, const MouseEvent&) { return true; }
|
||||
void MidiKeyboardComponent::mouseUpOnKey (int, const MouseEvent&) {}
|
||||
|
||||
void MidiKeyboardComponent::mouseDown (const MouseEvent& e)
|
||||
{
|
||||
float mousePositionVelocity;
|
||||
auto newNote = xyToNote (e.position, mousePositionVelocity);
|
||||
|
||||
if (newNote >= 0 && mouseDownOnKey (newNote, e))
|
||||
updateNoteUnderMouse (e, true);
|
||||
}
|
||||
|
||||
void MidiKeyboardComponent::mouseUp (const MouseEvent& e)
|
||||
{
|
||||
updateNoteUnderMouse (e, false);
|
||||
|
||||
float mousePositionVelocity;
|
||||
auto note = xyToNote (e.position, 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)
|
||||
{
|
||||
auto amount = (orientation == horizontalKeyboard && wheel.deltaX != 0)
|
||||
? wheel.deltaX : (orientation == verticalKeyboardFacingLeft ? wheel.deltaY
|
||||
: -wheel.deltaY);
|
||||
|
||||
setLowestVisibleKeyFloat (firstKey - amount * keyWidth);
|
||||
}
|
||||
|
||||
void MidiKeyboardComponent::timerCallback()
|
||||
{
|
||||
if (noPendingUpdates.exchange (true))
|
||||
return;
|
||||
|
||||
for (int i = rangeStart; i <= rangeEnd; ++i)
|
||||
{
|
||||
bool isOn = state.isNoteOnForChannels (midiInChannelMask, i);
|
||||
|
||||
if (keysCurrentlyDrawnDown[i] != isOn)
|
||||
{
|
||||
keysCurrentlyDrawnDown.setBit (i, isOn);
|
||||
repaintNote (i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void MidiKeyboardComponent::clearKeyMappings()
|
||||
{
|
||||
resetAnyKeysInUse();
|
||||
keyPressNotes.clear();
|
||||
keyPresses.clear();
|
||||
}
|
||||
|
||||
void MidiKeyboardComponent::setKeyPressForNote (const KeyPress& key, int midiNoteOffsetFromC)
|
||||
{
|
||||
removeKeyPressForNote (midiNoteOffsetFromC);
|
||||
|
||||
keyPressNotes.add (midiNoteOffsetFromC);
|
||||
keyPresses.add (key);
|
||||
}
|
||||
|
||||
void MidiKeyboardComponent::removeKeyPressForNote (int midiNoteOffsetFromC)
|
||||
{
|
||||
for (int i = keyPressNotes.size(); --i >= 0;)
|
||||
{
|
||||
if (keyPressNotes.getUnchecked (i) == midiNoteOffsetFromC)
|
||||
{
|
||||
keyPressNotes.remove (i);
|
||||
keyPresses.remove (i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MidiKeyboardComponent::setKeyPressBaseOctave (int newOctaveNumber)
|
||||
{
|
||||
jassert (newOctaveNumber >= 0 && newOctaveNumber <= 10);
|
||||
|
||||
keyMappingOctave = newOctaveNumber;
|
||||
}
|
||||
|
||||
bool MidiKeyboardComponent::keyStateChanged (bool /*isKeyDown*/)
|
||||
{
|
||||
bool keyPressUsed = false;
|
||||
|
||||
for (int i = keyPresses.size(); --i >= 0;)
|
||||
{
|
||||
auto 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, 0.0f);
|
||||
keyPressUsed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return keyPressUsed;
|
||||
}
|
||||
|
||||
bool MidiKeyboardComponent::keyPressed (const KeyPress& key)
|
||||
{
|
||||
return keyPresses.contains (key);
|
||||
}
|
||||
|
||||
void MidiKeyboardComponent::focusLost (FocusChangeType)
|
||||
{
|
||||
resetAnyKeysInUse();
|
||||
}
|
||||
|
||||
} // namespace juce
|
445
deps/juce/modules/juce_audio_utils/gui/juce_MidiKeyboardComponent.h
vendored
Normal file
445
deps/juce/modules/juce_audio_utils/gui/juce_MidiKeyboardComponent.h
vendored
Normal file
@ -0,0 +1,445 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
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 qwerty 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
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class JUCE_API MidiKeyboardComponent : public Component,
|
||||
public MidiKeyboardState::Listener,
|
||||
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 horizontal or vertical
|
||||
*/
|
||||
MidiKeyboardComponent (MidiKeyboardState& state,
|
||||
Orientation orientation);
|
||||
|
||||
/** Destructor. */
|
||||
~MidiKeyboardComponent() override;
|
||||
|
||||
//==============================================================================
|
||||
/** 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 width used to draw the buttons that scroll the keyboard up/down in octaves. */
|
||||
void setScrollButtonWidth (int widthInPixels);
|
||||
|
||||
/** Returns the width that was set by setScrollButtonWidth(). */
|
||||
int getScrollButtonWidth() const noexcept { return scrollButtonWidth; }
|
||||
|
||||
/** 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; }
|
||||
|
||||
/** Sets the length of the black notes as a proportion of the white note length. */
|
||||
void setBlackNoteLengthProportion (float ratio) noexcept;
|
||||
|
||||
/** Returns the length of the black notes as a proportion of the white note length. */
|
||||
float getBlackNoteLengthProportion() const noexcept { return blackNoteLengthRatio; }
|
||||
|
||||
/** Returns the absolute length of the black notes.
|
||||
This will be their vertical or horizontal length, depending on the keyboard's orientation.
|
||||
*/
|
||||
float getBlackNoteLength() const noexcept;
|
||||
|
||||
/** Sets the width of the black notes as a proportion of the white note width. */
|
||||
void setBlackNoteWidthProportion (float ratio) noexcept;
|
||||
|
||||
/** Returns the width of the black notes as a proportion of the white note width. */
|
||||
float getBlackNoteWidthProportion() const noexcept { return blackNoteWidthRatio; }
|
||||
|
||||
/** Returns the absolute width of the black notes.
|
||||
This will be their vertical or horizontal width, depending on the keyboard's orientation.
|
||||
*/
|
||||
float getBlackNoteWidth() const noexcept { return keyWidth * blackNoteWidthRatio; }
|
||||
|
||||
/** 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);
|
||||
|
||||
/** Sets width of scroll buttons */
|
||||
void setScrollButtonsWidth (int width);
|
||||
|
||||
/** Returns width of scroll buttons */
|
||||
int getScrollButtonsWidth () const { return scrollButtonWidth; }
|
||||
|
||||
//==============================================================================
|
||||
/** 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.
|
||||
*/
|
||||
float getKeyStartPosition (int midiNoteNumber) const;
|
||||
|
||||
/** Returns the total width needed to fit all the keys in the available range. */
|
||||
float getTotalKeyboardWidth() const noexcept;
|
||||
|
||||
/** Returns the key at a given coordinate. */
|
||||
int getNoteAtPosition (Point<float> 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 */
|
||||
bool keyPressed (const KeyPress&) 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, float velocity) 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, Rectangle<float> area,
|
||||
bool isDown, bool isOver,
|
||||
Colour lineColour, 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, Rectangle<float> area,
|
||||
bool isDown, bool isOver,
|
||||
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 (int midiNoteNumber);
|
||||
|
||||
/** Draws the up and down buttons that scroll the keyboard up/down in octaves. */
|
||||
virtual void drawUpDownButton (Graphics& g, int w, int h,
|
||||
bool isMouseOver,
|
||||
bool isButtonPressed,
|
||||
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.
|
||||
|
||||
Return true if you want the drag to trigger the new note, or false if you
|
||||
want to handle it yourself and not have the note played.
|
||||
|
||||
@see mouseDownOnKey
|
||||
*/
|
||||
virtual bool 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 position 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()
|
||||
@returns the start and length of the key along the axis of the keyboard
|
||||
*/
|
||||
virtual Range<float> getKeyPosition (int midiNoteNumber, float keyWidth) const;
|
||||
|
||||
/** Returns the rectangle for a given key if within the displayable range */
|
||||
Rectangle<float> getRectangleForKey (int midiNoteNumber) const;
|
||||
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
struct UpDownButton;
|
||||
|
||||
MidiKeyboardState& state;
|
||||
float blackNoteLengthRatio = 0.7f;
|
||||
float blackNoteWidthRatio = 0.7f;
|
||||
float xOffset = 0;
|
||||
float keyWidth = 16.0f;
|
||||
int scrollButtonWidth = 12;
|
||||
Orientation orientation;
|
||||
|
||||
int midiChannel = 1, midiInChannelMask = 0xffff;
|
||||
float velocity = 1.0f;
|
||||
|
||||
Array<int> mouseOverNotes, mouseDownNotes;
|
||||
BigInteger keysPressed, keysCurrentlyDrawnDown;
|
||||
std::atomic<bool> noPendingUpdates { true };
|
||||
|
||||
int rangeStart = 0, rangeEnd = 127;
|
||||
float firstKey = 12 * 4.0f;
|
||||
bool canScroll = true, useMousePositionForVelocity = true;
|
||||
std::unique_ptr<Button> scrollDown, scrollUp;
|
||||
|
||||
Array<KeyPress> keyPresses;
|
||||
Array<int> keyPressNotes;
|
||||
int keyMappingOctave = 6, octaveNumForMiddleC = 3;
|
||||
|
||||
Range<float> getKeyPos (int midiNoteNumber) const;
|
||||
int xyToNote (Point<float>, float& mousePositionVelocity);
|
||||
int remappedXYToNote (Point<float>, float& mousePositionVelocity) const;
|
||||
void resetAnyKeysInUse();
|
||||
void updateNoteUnderMouse (Point<float>, bool isDown, int fingerNum);
|
||||
void updateNoteUnderMouse (const MouseEvent&, bool isDown);
|
||||
void repaintNote (int midiNoteNumber);
|
||||
void setLowestVisibleKeyFloat (float noteNumber);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiKeyboardComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
102
deps/juce/modules/juce_audio_utils/juce_audio_utils.cpp
vendored
Normal file
102
deps/juce/modules/juce_audio_utils/juce_audio_utils.cpp
vendored
Normal file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifdef JUCE_AUDIO_UTILS_H_INCLUDED
|
||||
/* 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
|
||||
|
||||
#define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1
|
||||
#define JUCE_CORE_INCLUDE_JNI_HELPERS 1
|
||||
#define JUCE_CORE_INCLUDE_OBJC_HELPERS 1
|
||||
#define JUCE_CORE_INCLUDE_COM_SMART_PTR 1
|
||||
|
||||
#include "juce_audio_utils.h"
|
||||
#include <juce_gui_extra/juce_gui_extra.h>
|
||||
|
||||
#if JUCE_MAC
|
||||
#import <DiscRecording/DiscRecording.h>
|
||||
#import <CoreAudioKit/CABTLEMIDIWindowController.h>
|
||||
#elif JUCE_WINDOWS
|
||||
#if JUCE_USE_CDBURNER
|
||||
/* You'll need the Platform SDK for these headers - if you don't have it and don't
|
||||
need to use CD-burning, then you might just want to set the JUCE_USE_CDBURNER flag
|
||||
to 0, to avoid these includes.
|
||||
*/
|
||||
#include <imapi.h>
|
||||
#include <imapierror.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include "gui/juce_AudioDeviceSelectorComponent.cpp"
|
||||
#include "gui/juce_AudioThumbnail.cpp"
|
||||
#include "gui/juce_AudioThumbnailCache.cpp"
|
||||
#include "gui/juce_AudioVisualiserComponent.cpp"
|
||||
#include "gui/juce_MidiKeyboardComponent.cpp"
|
||||
#include "gui/juce_AudioAppComponent.cpp"
|
||||
#include "players/juce_SoundPlayer.cpp"
|
||||
#include "players/juce_AudioProcessorPlayer.cpp"
|
||||
#include "audio_cd/juce_AudioCDReader.cpp"
|
||||
|
||||
#if JUCE_MAC
|
||||
#include "native/juce_mac_BluetoothMidiDevicePairingDialogue.mm"
|
||||
|
||||
#if JUCE_USE_CDREADER
|
||||
#include "native/juce_mac_AudioCDReader.mm"
|
||||
#endif
|
||||
|
||||
#if JUCE_USE_CDBURNER
|
||||
#include "native/juce_mac_AudioCDBurner.mm"
|
||||
#endif
|
||||
|
||||
#elif JUCE_IOS
|
||||
#include "native/juce_ios_BluetoothMidiDevicePairingDialogue.mm"
|
||||
|
||||
#elif JUCE_ANDROID
|
||||
#include "native/juce_android_BluetoothMidiDevicePairingDialogue.cpp"
|
||||
|
||||
#elif JUCE_LINUX || JUCE_BSD
|
||||
#if JUCE_USE_CDREADER
|
||||
#include "native/juce_linux_AudioCDReader.cpp"
|
||||
#endif
|
||||
|
||||
#include "native/juce_linux_BluetoothMidiDevicePairingDialogue.cpp"
|
||||
|
||||
#elif JUCE_WINDOWS
|
||||
#include "native/juce_win_BluetoothMidiDevicePairingDialogue.cpp"
|
||||
|
||||
#if JUCE_USE_CDREADER
|
||||
#include "native/juce_win32_AudioCDReader.cpp"
|
||||
#endif
|
||||
|
||||
#if JUCE_USE_CDBURNER
|
||||
#include "native/juce_win32_AudioCDBurner.cpp"
|
||||
#endif
|
||||
|
||||
#endif
|
89
deps/juce/modules/juce_audio_utils/juce_audio_utils.h
vendored
Normal file
89
deps/juce/modules/juce_audio_utils/juce_audio_utils.h
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
The block below describes the properties of this module, and is read by
|
||||
the Projucer to automatically generate project code that uses it.
|
||||
For details about the syntax and how to create or use a module, see the
|
||||
JUCE Module Format.md file.
|
||||
|
||||
|
||||
BEGIN_JUCE_MODULE_DECLARATION
|
||||
|
||||
ID: juce_audio_utils
|
||||
vendor: juce
|
||||
version: 6.1.2
|
||||
name: JUCE extra audio utility classes
|
||||
description: Classes for audio-related GUI and miscellaneous tasks.
|
||||
website: http://www.juce.com/juce
|
||||
license: GPL/Commercial
|
||||
minimumCppStandard: 14
|
||||
|
||||
dependencies: juce_audio_processors, juce_audio_formats, juce_audio_devices
|
||||
OSXFrameworks: CoreAudioKit DiscRecording
|
||||
iOSFrameworks: CoreAudioKit
|
||||
|
||||
END_JUCE_MODULE_DECLARATION
|
||||
|
||||
*******************************************************************************/
|
||||
|
||||
|
||||
#pragma once
|
||||
#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>
|
||||
|
||||
//==============================================================================
|
||||
/** Config: JUCE_USE_CDREADER
|
||||
Enables the AudioCDReader class (on supported platforms).
|
||||
*/
|
||||
#ifndef JUCE_USE_CDREADER
|
||||
#define JUCE_USE_CDREADER 0
|
||||
#endif
|
||||
|
||||
/** Config: JUCE_USE_CDBURNER
|
||||
Enables the AudioCDBurner class (on supported platforms).
|
||||
*/
|
||||
#ifndef JUCE_USE_CDBURNER
|
||||
#define JUCE_USE_CDBURNER 0
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
#include "gui/juce_AudioDeviceSelectorComponent.h"
|
||||
#include "gui/juce_AudioThumbnailBase.h"
|
||||
#include "gui/juce_AudioThumbnail.h"
|
||||
#include "gui/juce_AudioThumbnailCache.h"
|
||||
#include "gui/juce_AudioVisualiserComponent.h"
|
||||
#include "gui/juce_MidiKeyboardComponent.h"
|
||||
#include "gui/juce_AudioAppComponent.h"
|
||||
#include "gui/juce_BluetoothMidiDevicePairingDialogue.h"
|
||||
#include "players/juce_SoundPlayer.h"
|
||||
#include "players/juce_AudioProcessorPlayer.h"
|
||||
#include "audio_cd/juce_AudioCDBurner.h"
|
||||
#include "audio_cd/juce_AudioCDReader.h"
|
26
deps/juce/modules/juce_audio_utils/juce_audio_utils.mm
vendored
Normal file
26
deps/juce/modules/juce_audio_utils/juce_audio_utils.mm
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#include "juce_audio_utils.cpp"
|
524
deps/juce/modules/juce_audio_utils/native/juce_android_BluetoothMidiDevicePairingDialogue.cpp
vendored
Normal file
524
deps/juce/modules/juce_audio_utils/native/juce_android_BluetoothMidiDevicePairingDialogue.cpp
vendored
Normal file
@ -0,0 +1,524 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
STATICMETHOD (getAndroidBluetoothManager, "getAndroidBluetoothManager", "(Landroid/content/Context;)Lcom/rmsl/juce/JuceMidiSupport$BluetoothManager;")
|
||||
|
||||
DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidJuceMidiSupport, "com/rmsl/juce/JuceMidiSupport", 23)
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
METHOD (getMidiBluetoothAddresses, "getMidiBluetoothAddresses", "()[Ljava/lang/String;") \
|
||||
METHOD (pairBluetoothMidiDevice, "pairBluetoothMidiDevice", "(Ljava/lang/String;)Z") \
|
||||
METHOD (unpairBluetoothMidiDevice, "unpairBluetoothMidiDevice", "(Ljava/lang/String;)V") \
|
||||
METHOD (getHumanReadableStringForBluetoothAddress, "getHumanReadableStringForBluetoothAddress", "(Ljava/lang/String;)Ljava/lang/String;") \
|
||||
METHOD (getBluetoothDeviceStatus, "getBluetoothDeviceStatus", "(Ljava/lang/String;)I") \
|
||||
METHOD (startStopScan, "startStopScan", "(Z)V")
|
||||
|
||||
DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidBluetoothManager, "com/rmsl/juce/JuceMidiSupport$BluetoothManager", 23)
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
//==============================================================================
|
||||
struct AndroidBluetoothMidiInterface
|
||||
{
|
||||
static void startStopScan (bool startScanning)
|
||||
{
|
||||
JNIEnv* env = getEnv();
|
||||
LocalRef<jobject> btManager (env->CallStaticObjectMethod (AndroidJuceMidiSupport, AndroidJuceMidiSupport.getAndroidBluetoothManager, getAppContext().get()));
|
||||
|
||||
if (btManager.get() != nullptr)
|
||||
env->CallVoidMethod (btManager.get(), AndroidBluetoothManager.startStopScan, (jboolean) (startScanning ? 1 : 0));
|
||||
}
|
||||
|
||||
static StringArray getBluetoothMidiDevicesNearby()
|
||||
{
|
||||
StringArray retval;
|
||||
|
||||
JNIEnv* env = getEnv();
|
||||
|
||||
LocalRef<jobject> btManager (env->CallStaticObjectMethod (AndroidJuceMidiSupport, AndroidJuceMidiSupport.getAndroidBluetoothManager, getAppContext().get()));
|
||||
|
||||
// if this is null then bluetooth is not enabled
|
||||
if (btManager.get() == nullptr)
|
||||
return {};
|
||||
|
||||
jobjectArray jDevices = (jobjectArray) env->CallObjectMethod (btManager.get(),
|
||||
AndroidBluetoothManager.getMidiBluetoothAddresses);
|
||||
LocalRef<jobjectArray> devices (jDevices);
|
||||
|
||||
const int count = env->GetArrayLength (devices.get());
|
||||
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
LocalRef<jstring> string ((jstring) env->GetObjectArrayElement (devices.get(), i));
|
||||
retval.add (juceString (string));
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static bool pairBluetoothMidiDevice (const String& bluetoothAddress)
|
||||
{
|
||||
JNIEnv* env = getEnv();
|
||||
|
||||
LocalRef<jobject> btManager (env->CallStaticObjectMethod (AndroidJuceMidiSupport, AndroidJuceMidiSupport.getAndroidBluetoothManager, getAppContext().get()));
|
||||
if (btManager.get() == nullptr)
|
||||
return false;
|
||||
|
||||
jboolean result = env->CallBooleanMethod (btManager.get(), AndroidBluetoothManager.pairBluetoothMidiDevice,
|
||||
javaString (bluetoothAddress).get());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void unpairBluetoothMidiDevice (const String& bluetoothAddress)
|
||||
{
|
||||
JNIEnv* env = getEnv();
|
||||
|
||||
LocalRef<jobject> btManager (env->CallStaticObjectMethod (AndroidJuceMidiSupport, AndroidJuceMidiSupport.getAndroidBluetoothManager, getAppContext().get()));
|
||||
|
||||
if (btManager.get() != nullptr)
|
||||
env->CallVoidMethod (btManager.get(), AndroidBluetoothManager.unpairBluetoothMidiDevice,
|
||||
javaString (bluetoothAddress).get());
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static String getHumanReadableStringForBluetoothAddress (const String& address)
|
||||
{
|
||||
JNIEnv* env = getEnv();
|
||||
|
||||
LocalRef<jobject> btManager (env->CallStaticObjectMethod (AndroidJuceMidiSupport, AndroidJuceMidiSupport.getAndroidBluetoothManager, getAppContext().get()));
|
||||
|
||||
if (btManager.get() == nullptr)
|
||||
return address;
|
||||
|
||||
LocalRef<jstring> string ((jstring) env->CallObjectMethod (btManager.get(),
|
||||
AndroidBluetoothManager.getHumanReadableStringForBluetoothAddress,
|
||||
javaString (address).get()));
|
||||
|
||||
|
||||
if (string.get() == nullptr)
|
||||
return address;
|
||||
|
||||
return juceString (string);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
enum PairStatus
|
||||
{
|
||||
unpaired = 0,
|
||||
paired = 1,
|
||||
pairing = 2
|
||||
};
|
||||
|
||||
static PairStatus isBluetoothDevicePaired (const String& address)
|
||||
{
|
||||
JNIEnv* env = getEnv();
|
||||
|
||||
LocalRef<jobject> btManager (env->CallStaticObjectMethod (AndroidJuceMidiSupport, AndroidJuceMidiSupport.getAndroidBluetoothManager, getAppContext().get()));
|
||||
|
||||
if (btManager.get() == nullptr)
|
||||
return unpaired;
|
||||
|
||||
return static_cast<PairStatus> (env->CallIntMethod (btManager.get(), AndroidBluetoothManager.getBluetoothDeviceStatus,
|
||||
javaString (address).get()));
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct AndroidBluetoothMidiDevice
|
||||
{
|
||||
enum ConnectionStatus
|
||||
{
|
||||
offline,
|
||||
connected,
|
||||
disconnected,
|
||||
connecting,
|
||||
disconnecting
|
||||
};
|
||||
|
||||
AndroidBluetoothMidiDevice (String deviceName, String address, ConnectionStatus status)
|
||||
: name (deviceName), bluetoothAddress (address), connectionStatus (status)
|
||||
{
|
||||
// can't create a device without a valid name and bluetooth address!
|
||||
jassert (! name.isEmpty());
|
||||
jassert (! bluetoothAddress.isEmpty());
|
||||
}
|
||||
|
||||
bool operator== (const AndroidBluetoothMidiDevice& other) const noexcept
|
||||
{
|
||||
return bluetoothAddress == other.bluetoothAddress;
|
||||
}
|
||||
|
||||
bool operator!= (const AndroidBluetoothMidiDevice& other) const noexcept
|
||||
{
|
||||
return ! operator== (other);
|
||||
}
|
||||
|
||||
const String name, bluetoothAddress;
|
||||
ConnectionStatus connectionStatus;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class AndroidBluetoothMidiDevicesListBox : public ListBox,
|
||||
private ListBoxModel,
|
||||
private Timer
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
AndroidBluetoothMidiDevicesListBox()
|
||||
: timerPeriodInMs (1000)
|
||||
{
|
||||
setRowHeight (40);
|
||||
setModel (this);
|
||||
setOutlineThickness (1);
|
||||
startTimer (timerPeriodInMs);
|
||||
}
|
||||
|
||||
void pairDeviceThreadFinished() // callback from PairDeviceThread
|
||||
{
|
||||
updateDeviceList();
|
||||
startTimer (timerPeriodInMs);
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
typedef AndroidBluetoothMidiDevice::ConnectionStatus DeviceStatus;
|
||||
|
||||
int getNumRows() override
|
||||
{
|
||||
return devices.size();
|
||||
}
|
||||
|
||||
void paintListBoxItem (int rowNumber, Graphics& g,
|
||||
int width, int height, bool) override
|
||||
{
|
||||
if (isPositiveAndBelow (rowNumber, devices.size()))
|
||||
{
|
||||
const AndroidBluetoothMidiDevice& device = devices.getReference (rowNumber);
|
||||
const String statusString (getDeviceStatusString (device.connectionStatus));
|
||||
|
||||
g.fillAll (Colours::white);
|
||||
|
||||
const float xmargin = 3.0f;
|
||||
const float ymargin = 3.0f;
|
||||
const float fontHeight = 0.4f * (float) height;
|
||||
const float deviceNameWidth = 0.6f * (float) width;
|
||||
|
||||
g.setFont (fontHeight);
|
||||
|
||||
g.setColour (getDeviceNameFontColour (device.connectionStatus));
|
||||
g.drawText (device.name,
|
||||
Rectangle<float> (xmargin, ymargin, deviceNameWidth - (2.0f * xmargin), (float) height - (2.0f * ymargin)),
|
||||
Justification::topLeft, true);
|
||||
|
||||
g.setColour (getDeviceStatusFontColour (device.connectionStatus));
|
||||
g.drawText (statusString,
|
||||
Rectangle<float> (deviceNameWidth + xmargin, ymargin,
|
||||
(float) width - deviceNameWidth - (2.0f * xmargin), (float) height - (2.0f * ymargin)),
|
||||
Justification::topRight, true);
|
||||
|
||||
g.setColour (Colours::grey);
|
||||
g.drawHorizontalLine (height - 1, xmargin, (float) width);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static Colour getDeviceNameFontColour (DeviceStatus deviceStatus) noexcept
|
||||
{
|
||||
if (deviceStatus == AndroidBluetoothMidiDevice::offline)
|
||||
return Colours::grey;
|
||||
|
||||
return Colours::black;
|
||||
}
|
||||
|
||||
static Colour getDeviceStatusFontColour (DeviceStatus deviceStatus) noexcept
|
||||
{
|
||||
if (deviceStatus == AndroidBluetoothMidiDevice::offline
|
||||
|| deviceStatus == AndroidBluetoothMidiDevice::connecting
|
||||
|| deviceStatus == AndroidBluetoothMidiDevice::disconnecting)
|
||||
return Colours::grey;
|
||||
|
||||
if (deviceStatus == AndroidBluetoothMidiDevice::connected)
|
||||
return Colours::green;
|
||||
|
||||
return Colours::black;
|
||||
}
|
||||
|
||||
static String getDeviceStatusString (DeviceStatus deviceStatus) noexcept
|
||||
{
|
||||
if (deviceStatus == AndroidBluetoothMidiDevice::offline) return "Offline";
|
||||
if (deviceStatus == AndroidBluetoothMidiDevice::connected) return "Connected";
|
||||
if (deviceStatus == AndroidBluetoothMidiDevice::disconnected) return "Not connected";
|
||||
if (deviceStatus == AndroidBluetoothMidiDevice::connecting) return "Connecting...";
|
||||
if (deviceStatus == AndroidBluetoothMidiDevice::disconnecting) return "Disconnecting...";
|
||||
|
||||
// unknown device state!
|
||||
jassertfalse;
|
||||
return "Status unknown";
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void listBoxItemClicked (int row, const MouseEvent&) override
|
||||
{
|
||||
const AndroidBluetoothMidiDevice& device = devices.getReference (row);
|
||||
|
||||
if (device.connectionStatus == AndroidBluetoothMidiDevice::disconnected)
|
||||
disconnectedDeviceClicked (row);
|
||||
|
||||
else if (device.connectionStatus == AndroidBluetoothMidiDevice::connected)
|
||||
connectedDeviceClicked (row);
|
||||
}
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
updateDeviceList();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct PairDeviceThread : public Thread,
|
||||
private AsyncUpdater
|
||||
{
|
||||
PairDeviceThread (const String& bluetoothAddressOfDeviceToPair,
|
||||
AndroidBluetoothMidiDevicesListBox& ownerListBox)
|
||||
: Thread ("JUCE Bluetooth MIDI Device Pairing Thread"),
|
||||
bluetoothAddress (bluetoothAddressOfDeviceToPair),
|
||||
owner (&ownerListBox)
|
||||
{
|
||||
startThread();
|
||||
}
|
||||
|
||||
void run() override
|
||||
{
|
||||
AndroidBluetoothMidiInterface::pairBluetoothMidiDevice (bluetoothAddress);
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
|
||||
void handleAsyncUpdate() override
|
||||
{
|
||||
if (owner != nullptr)
|
||||
owner->pairDeviceThreadFinished();
|
||||
|
||||
delete this;
|
||||
}
|
||||
|
||||
private:
|
||||
String bluetoothAddress;
|
||||
Component::SafePointer<AndroidBluetoothMidiDevicesListBox> owner;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
void disconnectedDeviceClicked (int row)
|
||||
{
|
||||
stopTimer();
|
||||
|
||||
AndroidBluetoothMidiDevice& device = devices.getReference (row);
|
||||
device.connectionStatus = AndroidBluetoothMidiDevice::connecting;
|
||||
updateContent();
|
||||
repaint();
|
||||
|
||||
new PairDeviceThread (device.bluetoothAddress, *this);
|
||||
}
|
||||
|
||||
void connectedDeviceClicked (int row)
|
||||
{
|
||||
AndroidBluetoothMidiDevice& device = devices.getReference (row);
|
||||
device.connectionStatus = AndroidBluetoothMidiDevice::disconnecting;
|
||||
updateContent();
|
||||
repaint();
|
||||
AndroidBluetoothMidiInterface::unpairBluetoothMidiDevice (device.bluetoothAddress);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void updateDeviceList()
|
||||
{
|
||||
StringArray bluetoothAddresses = AndroidBluetoothMidiInterface::getBluetoothMidiDevicesNearby();
|
||||
|
||||
Array<AndroidBluetoothMidiDevice> newDevices;
|
||||
|
||||
for (String* address = bluetoothAddresses.begin();
|
||||
address != bluetoothAddresses.end(); ++address)
|
||||
{
|
||||
String name = AndroidBluetoothMidiInterface::getHumanReadableStringForBluetoothAddress (*address);
|
||||
|
||||
DeviceStatus status;
|
||||
switch (AndroidBluetoothMidiInterface::isBluetoothDevicePaired (*address))
|
||||
{
|
||||
case AndroidBluetoothMidiInterface::pairing:
|
||||
status = AndroidBluetoothMidiDevice::connecting;
|
||||
break;
|
||||
case AndroidBluetoothMidiInterface::paired:
|
||||
status = AndroidBluetoothMidiDevice::connected;
|
||||
break;
|
||||
case AndroidBluetoothMidiInterface::unpaired:
|
||||
default:
|
||||
status = AndroidBluetoothMidiDevice::disconnected;
|
||||
}
|
||||
|
||||
newDevices.add (AndroidBluetoothMidiDevice (name, *address, status));
|
||||
}
|
||||
|
||||
devices.swapWith (newDevices);
|
||||
updateContent();
|
||||
repaint();
|
||||
}
|
||||
|
||||
Array<AndroidBluetoothMidiDevice> devices;
|
||||
const int timerPeriodInMs;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class BluetoothMidiSelectorOverlay : public Component
|
||||
{
|
||||
public:
|
||||
BluetoothMidiSelectorOverlay (ModalComponentManager::Callback* exitCallbackToUse,
|
||||
const Rectangle<int>& boundsToUse)
|
||||
: bounds (boundsToUse)
|
||||
{
|
||||
std::unique_ptr<ModalComponentManager::Callback> exitCallback (exitCallbackToUse);
|
||||
|
||||
AndroidBluetoothMidiInterface::startStopScan (true);
|
||||
|
||||
setAlwaysOnTop (true);
|
||||
setVisible (true);
|
||||
addToDesktop (ComponentPeer::windowHasDropShadow);
|
||||
|
||||
if (bounds.isEmpty())
|
||||
setBounds (0, 0, getParentWidth(), getParentHeight());
|
||||
else
|
||||
setBounds (bounds);
|
||||
|
||||
toFront (true);
|
||||
setOpaque (! bounds.isEmpty());
|
||||
|
||||
addAndMakeVisible (bluetoothDevicesList);
|
||||
enterModalState (true, exitCallback.release(), true);
|
||||
}
|
||||
|
||||
~BluetoothMidiSelectorOverlay() override
|
||||
{
|
||||
AndroidBluetoothMidiInterface::startStopScan (false);
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
g.fillAll (bounds.isEmpty() ? Colours::black.withAlpha (0.6f) : Colours::black);
|
||||
|
||||
g.setColour (Colour (0xffdfdfdf));
|
||||
Rectangle<int> overlayBounds = getOverlayBounds();
|
||||
g.fillRect (overlayBounds);
|
||||
|
||||
g.setColour (Colours::black);
|
||||
g.setFont (16);
|
||||
g.drawText ("Bluetooth MIDI Devices",
|
||||
overlayBounds.removeFromTop (20).reduced (3, 3),
|
||||
Justification::topLeft, true);
|
||||
|
||||
overlayBounds.removeFromTop (2);
|
||||
|
||||
g.setFont (12);
|
||||
g.drawText ("tap to connect/disconnect",
|
||||
overlayBounds.removeFromTop (18).reduced (3, 3),
|
||||
Justification::topLeft, true);
|
||||
}
|
||||
|
||||
void inputAttemptWhenModal() override { exitModalState (0); }
|
||||
void mouseDrag (const MouseEvent&) override {}
|
||||
void mouseDown (const MouseEvent&) override { exitModalState (0); }
|
||||
void resized() override { update(); }
|
||||
void parentSizeChanged() override { update(); }
|
||||
|
||||
private:
|
||||
Rectangle<int> bounds;
|
||||
|
||||
void update()
|
||||
{
|
||||
if (bounds.isEmpty())
|
||||
setBounds (0, 0, getParentWidth(), getParentHeight());
|
||||
else
|
||||
setBounds (bounds);
|
||||
|
||||
bluetoothDevicesList.setBounds (getOverlayBounds().withTrimmedTop (40));
|
||||
}
|
||||
|
||||
Rectangle<int> getOverlayBounds() const noexcept
|
||||
{
|
||||
if (bounds.isEmpty())
|
||||
{
|
||||
const int pw = getParentWidth();
|
||||
const int ph = getParentHeight();
|
||||
|
||||
return Rectangle<int> (pw, ph).withSizeKeepingCentre (jmin (400, pw - 14),
|
||||
jmin (300, ph - 40));
|
||||
}
|
||||
|
||||
return bounds.withZeroOrigin();
|
||||
}
|
||||
|
||||
AndroidBluetoothMidiDevicesListBox bluetoothDevicesList;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BluetoothMidiSelectorOverlay)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
bool BluetoothMidiDevicePairingDialogue::open (ModalComponentManager::Callback* exitCallbackPtr,
|
||||
Rectangle<int>* btBounds)
|
||||
{
|
||||
std::unique_ptr<ModalComponentManager::Callback> exitCallback (exitCallbackPtr);
|
||||
|
||||
if (getAndroidSDKVersion() < 23)
|
||||
return false;
|
||||
|
||||
auto boundsToUse = (btBounds != nullptr ? *btBounds : Rectangle<int> {});
|
||||
|
||||
if (! RuntimePermissions::isGranted (RuntimePermissions::bluetoothMidi))
|
||||
{
|
||||
// If you hit this assert, you probably forgot to get RuntimePermissions::bluetoothMidi.
|
||||
// This is not going to work, boo! The pairing dialogue won't be able to scan for or
|
||||
// find any devices, it will just display an empty list, so don't bother opening it.
|
||||
jassertfalse;
|
||||
return false;
|
||||
}
|
||||
|
||||
new BluetoothMidiSelectorOverlay (exitCallback.release(), boundsToUse);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BluetoothMidiDevicePairingDialogue::isAvailable()
|
||||
{
|
||||
if (getAndroidSDKVersion() < 23)
|
||||
return false;
|
||||
|
||||
auto* env = getEnv();
|
||||
|
||||
LocalRef<jobject> btManager (env->CallStaticObjectMethod (AndroidJuceMidiSupport, AndroidJuceMidiSupport.getAndroidBluetoothManager, getAppContext().get()));
|
||||
return btManager != nullptr;
|
||||
}
|
||||
|
||||
} // namespace juce
|
148
deps/juce/modules/juce_audio_utils/native/juce_ios_BluetoothMidiDevicePairingDialogue.mm
vendored
Normal file
148
deps/juce/modules/juce_audio_utils/native/juce_ios_BluetoothMidiDevicePairingDialogue.mm
vendored
Normal file
@ -0,0 +1,148 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#if ! TARGET_IPHONE_SIMULATOR
|
||||
|
||||
#include <CoreAudioKit/CoreAudioKit.h>
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
class BluetoothMidiSelectorOverlay : public Component
|
||||
{
|
||||
public:
|
||||
BluetoothMidiSelectorOverlay (ModalComponentManager::Callback* exitCallbackToUse,
|
||||
const Rectangle<int>& boundsToUse)
|
||||
: bounds (boundsToUse)
|
||||
{
|
||||
std::unique_ptr<ModalComponentManager::Callback> exitCallback (exitCallbackToUse);
|
||||
|
||||
setAlwaysOnTop (true);
|
||||
setVisible (true);
|
||||
addToDesktop (ComponentPeer::windowHasDropShadow);
|
||||
|
||||
if (bounds.isEmpty())
|
||||
setBounds (0, 0, getParentWidth(), getParentHeight());
|
||||
else
|
||||
setBounds (bounds);
|
||||
|
||||
toFront (true);
|
||||
setOpaque (true);
|
||||
|
||||
controller = [[CABTMIDICentralViewController alloc] init];
|
||||
nativeSelectorComponent.setView ([controller view]);
|
||||
|
||||
addAndMakeVisible (nativeSelectorComponent);
|
||||
|
||||
enterModalState (true, exitCallback.release(), true);
|
||||
}
|
||||
|
||||
~BluetoothMidiSelectorOverlay() override
|
||||
{
|
||||
nativeSelectorComponent.setView (nullptr);
|
||||
[controller release];
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
g.fillAll (bounds.isEmpty() ? Colours::black.withAlpha (0.5f) : Colours::black);
|
||||
}
|
||||
|
||||
void inputAttemptWhenModal() override { close(); }
|
||||
void mouseDrag (const MouseEvent&) override {}
|
||||
void mouseDown (const MouseEvent&) override { close(); }
|
||||
void resized() override { update(); }
|
||||
void parentSizeChanged() override { update(); }
|
||||
|
||||
private:
|
||||
void update()
|
||||
{
|
||||
if (bounds.isEmpty())
|
||||
{
|
||||
const int pw = getParentWidth();
|
||||
const int ph = getParentHeight();
|
||||
|
||||
nativeSelectorComponent.setBounds (Rectangle<int> (pw, ph)
|
||||
.withSizeKeepingCentre (jmin (400, pw),
|
||||
jmin (450, ph - 40)));
|
||||
}
|
||||
else
|
||||
{
|
||||
nativeSelectorComponent.setBounds (bounds.withZeroOrigin());
|
||||
}
|
||||
}
|
||||
|
||||
void close()
|
||||
{
|
||||
exitModalState (0);
|
||||
setVisible (false);
|
||||
}
|
||||
|
||||
CABTMIDICentralViewController* controller;
|
||||
UIViewComponent nativeSelectorComponent;
|
||||
Rectangle<int> bounds;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BluetoothMidiSelectorOverlay)
|
||||
};
|
||||
|
||||
bool BluetoothMidiDevicePairingDialogue::open (ModalComponentManager::Callback* exitCallback,
|
||||
Rectangle<int>* btBounds)
|
||||
{
|
||||
std::unique_ptr<ModalComponentManager::Callback> cb (exitCallback);
|
||||
auto boundsToUse = (btBounds != nullptr ? *btBounds : Rectangle<int> {});
|
||||
|
||||
if (isAvailable())
|
||||
{
|
||||
new BluetoothMidiSelectorOverlay (cb.release(), boundsToUse);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BluetoothMidiDevicePairingDialogue::isAvailable()
|
||||
{
|
||||
return NSClassFromString (@"CABTMIDICentralViewController") != nil;
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
||||
//==============================================================================
|
||||
#else
|
||||
|
||||
namespace juce
|
||||
{
|
||||
bool BluetoothMidiDevicePairingDialogue::open (ModalComponentManager::Callback* exitCallback,
|
||||
Rectangle<int>*)
|
||||
{
|
||||
std::unique_ptr<ModalComponentManager::Callback> cb (exitCallback);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BluetoothMidiDevicePairingDialogue::isAvailable() { return false; }
|
||||
}
|
||||
|
||||
#endif
|
83
deps/juce/modules/juce_audio_utils/native/juce_linux_AudioCDReader.cpp
vendored
Normal file
83
deps/juce/modules/juce_audio_utils/native/juce_linux_AudioCDReader.cpp
vendored
Normal file
@ -0,0 +1,83 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
AudioCDReader::AudioCDReader()
|
||||
: AudioFormatReader (0, "CD Audio")
|
||||
{
|
||||
}
|
||||
|
||||
StringArray AudioCDReader::getAvailableCDNames()
|
||||
{
|
||||
StringArray names;
|
||||
return names;
|
||||
}
|
||||
|
||||
AudioCDReader* AudioCDReader::createReaderForCD (const int)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AudioCDReader::~AudioCDReader()
|
||||
{
|
||||
}
|
||||
|
||||
void AudioCDReader::refreshTrackLengths()
|
||||
{
|
||||
}
|
||||
|
||||
bool AudioCDReader::readSamples (int**, int, int,
|
||||
int64, int)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AudioCDReader::isCDStillPresent() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AudioCDReader::isTrackAudio (int) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void AudioCDReader::enableIndexScanning (bool)
|
||||
{
|
||||
}
|
||||
|
||||
int AudioCDReader::getLastIndex() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
Array<int> AudioCDReader::findIndexesInTrack (const int)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace juce
|
45
deps/juce/modules/juce_audio_utils/native/juce_linux_BluetoothMidiDevicePairingDialogue.cpp
vendored
Normal file
45
deps/juce/modules/juce_audio_utils/native/juce_linux_BluetoothMidiDevicePairingDialogue.cpp
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
bool BluetoothMidiDevicePairingDialogue::open (ModalComponentManager::Callback* exitCallback,
|
||||
Rectangle<int>*)
|
||||
{
|
||||
std::unique_ptr<ModalComponentManager::Callback> cb (exitCallback);
|
||||
// not implemented on Linux yet!
|
||||
// You should check whether the dialogue is available on your system
|
||||
// using isAvailable() before calling open().
|
||||
jassertfalse;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BluetoothMidiDevicePairingDialogue::isAvailable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace juce
|
466
deps/juce/modules/juce_audio_utils/native/juce_mac_AudioCDBurner.mm
vendored
Normal file
466
deps/juce/modules/juce_audio_utils/native/juce_mac_AudioCDBurner.mm
vendored
Normal file
@ -0,0 +1,466 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
const int kilobytesPerSecond1x = 176;
|
||||
|
||||
struct AudioTrackProducerClass : public ObjCClass<NSObject>
|
||||
{
|
||||
AudioTrackProducerClass() : ObjCClass<NSObject> ("JUCEAudioTrackProducer_")
|
||||
{
|
||||
addIvar<AudioSourceHolder*> ("source");
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
addMethod (@selector (initWithAudioSourceHolder:), initWithAudioSourceHolder, "@@:^v");
|
||||
addMethod (@selector (verifyDataForTrack:intoBuffer:length:atAddress:blockSize:ioFlags:),
|
||||
produceDataForTrack, "I@:@^cIQI^I");
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
addMethod (@selector (cleanupTrackAfterBurn:), cleanupTrackAfterBurn, "v@:@");
|
||||
addMethod (@selector (cleanupTrackAfterVerification:), cleanupTrackAfterVerification, "c@:@");
|
||||
addMethod (@selector (estimateLengthOfTrack:), estimateLengthOfTrack, "Q@:@");
|
||||
addMethod (@selector (prepareTrack:forBurn:toMedia:), prepareTrack, "c@:@@@");
|
||||
addMethod (@selector (prepareTrackForVerification:), prepareTrackForVerification, "c@:@");
|
||||
addMethod (@selector (produceDataForTrack:intoBuffer:length:atAddress:blockSize:ioFlags:),
|
||||
produceDataForTrack, "I@:@^cIQI^I");
|
||||
addMethod (@selector (producePreGapForTrack:intoBuffer:length:atAddress:blockSize:ioFlags:),
|
||||
produceDataForTrack, "I@:@^cIQI^I");
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
struct AudioSourceHolder
|
||||
{
|
||||
AudioSourceHolder (AudioSource* s, int numFrames)
|
||||
: source (s), readPosition (0), lengthInFrames (numFrames)
|
||||
{
|
||||
}
|
||||
|
||||
~AudioSourceHolder()
|
||||
{
|
||||
if (source != nullptr)
|
||||
source->releaseResources();
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioSource> source;
|
||||
int readPosition, lengthInFrames;
|
||||
};
|
||||
|
||||
private:
|
||||
static id initWithAudioSourceHolder (id self, SEL, AudioSourceHolder* source)
|
||||
{
|
||||
self = sendSuperclassMessage<id> (self, @selector (init));
|
||||
object_setInstanceVariable (self, "source", source);
|
||||
return self;
|
||||
}
|
||||
|
||||
static AudioSourceHolder* getSource (id self)
|
||||
{
|
||||
return getIvar<AudioSourceHolder*> (self, "source");
|
||||
}
|
||||
|
||||
static void dealloc (id self, SEL)
|
||||
{
|
||||
delete getSource (self);
|
||||
sendSuperclassMessage<void> (self, @selector (dealloc));
|
||||
}
|
||||
|
||||
static void cleanupTrackAfterBurn (id, SEL, DRTrack*) {}
|
||||
static BOOL cleanupTrackAfterVerification (id, SEL, DRTrack*) { return true; }
|
||||
|
||||
static uint64_t estimateLengthOfTrack (id self, SEL, DRTrack*)
|
||||
{
|
||||
return static_cast<uint64_t> (getSource (self)->lengthInFrames);
|
||||
}
|
||||
|
||||
static BOOL prepareTrack (id self, SEL, DRTrack*, DRBurn*, NSDictionary*)
|
||||
{
|
||||
if (AudioSourceHolder* const source = getSource (self))
|
||||
{
|
||||
source->source->prepareToPlay (44100 / 75, 44100);
|
||||
source->readPosition = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static BOOL prepareTrackForVerification (id self, SEL, DRTrack*)
|
||||
{
|
||||
if (AudioSourceHolder* const source = getSource (self))
|
||||
source->source->prepareToPlay (44100 / 75, 44100);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint32_t produceDataForTrack (id self, SEL, DRTrack*, char* buffer,
|
||||
uint32_t bufferLength, uint64_t /*address*/,
|
||||
uint32_t /*blockSize*/, uint32_t* /*flags*/)
|
||||
{
|
||||
if (AudioSourceHolder* const source = getSource (self))
|
||||
{
|
||||
const int numSamples = jmin ((int) bufferLength / 4,
|
||||
(source->lengthInFrames * (44100 / 75)) - source->readPosition);
|
||||
|
||||
if (numSamples > 0)
|
||||
{
|
||||
AudioBuffer<float> tempBuffer (2, numSamples);
|
||||
AudioSourceChannelInfo info (tempBuffer);
|
||||
|
||||
source->source->getNextAudioBlock (info);
|
||||
|
||||
typedef AudioData::Pointer <AudioData::Int16, AudioData::LittleEndian, AudioData::Interleaved, AudioData::NonConst> CDSampleFormat;
|
||||
typedef AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const> SourceSampleFormat;
|
||||
|
||||
CDSampleFormat left (buffer, 2);
|
||||
left.convertSamples (SourceSampleFormat (tempBuffer.getReadPointer (0)), numSamples);
|
||||
CDSampleFormat right (buffer + 2, 2);
|
||||
right.convertSamples (SourceSampleFormat (tempBuffer.getReadPointer (1)), numSamples);
|
||||
|
||||
source->readPosition += numSamples;
|
||||
}
|
||||
|
||||
return static_cast<uint32_t> (numSamples * 4);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint32_t producePreGapForTrack (id, SEL, DRTrack*, char* buffer,
|
||||
uint32_t bufferLength, uint64_t /*address*/,
|
||||
uint32_t /*blockSize*/, uint32_t* /*flags*/)
|
||||
{
|
||||
zeromem (buffer, bufferLength);
|
||||
return bufferLength;
|
||||
}
|
||||
|
||||
static BOOL verifyDataForTrack (id, SEL, DRTrack*, const char*,
|
||||
uint32_t /*bufferLength*/, uint64_t /*address*/,
|
||||
uint32_t /*blockSize*/, uint32_t* /*flags*/)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct OpenDiskDevice
|
||||
{
|
||||
OpenDiskDevice (DRDevice* d)
|
||||
: device (d),
|
||||
tracks ([[NSMutableArray alloc] init]),
|
||||
underrunProtection (true)
|
||||
{
|
||||
}
|
||||
|
||||
~OpenDiskDevice()
|
||||
{
|
||||
[tracks release];
|
||||
}
|
||||
|
||||
void addSourceTrack (AudioSource* source, int numSamples)
|
||||
{
|
||||
if (source != nullptr)
|
||||
{
|
||||
const int numFrames = (numSamples + 587) / 588;
|
||||
|
||||
static AudioTrackProducerClass cls;
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
NSObject* producer = [cls.createInstance() performSelector: @selector (initWithAudioSourceHolder:)
|
||||
withObject: (id) new AudioTrackProducerClass::AudioSourceHolder (source, numFrames)];
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
DRTrack* track = [[DRTrack alloc] initWithProducer: producer];
|
||||
|
||||
{
|
||||
NSMutableDictionary* p = [[track properties] mutableCopy];
|
||||
[p setObject: [DRMSF msfWithFrames: static_cast<UInt32> (numFrames)] forKey: DRTrackLengthKey];
|
||||
[p setObject: [NSNumber numberWithUnsignedShort: 2352] forKey: DRBlockSizeKey];
|
||||
[p setObject: [NSNumber numberWithInt: 0] forKey: DRDataFormKey];
|
||||
[p setObject: [NSNumber numberWithInt: 0] forKey: DRBlockTypeKey];
|
||||
[p setObject: [NSNumber numberWithInt: 0] forKey: DRTrackModeKey];
|
||||
[p setObject: [NSNumber numberWithInt: 0] forKey: DRSessionFormatKey];
|
||||
[track setProperties: p];
|
||||
[p release];
|
||||
}
|
||||
|
||||
[tracks addObject: track];
|
||||
|
||||
[track release];
|
||||
[producer release];
|
||||
}
|
||||
}
|
||||
|
||||
String burn (AudioCDBurner::BurnProgressListener* listener,
|
||||
bool shouldEject, bool peformFakeBurnForTesting, int burnSpeed)
|
||||
{
|
||||
DRBurn* burn = [DRBurn burnForDevice: device];
|
||||
|
||||
if (! [device acquireExclusiveAccess])
|
||||
return "Couldn't open or write to the CD device";
|
||||
|
||||
[device acquireMediaReservation];
|
||||
|
||||
NSMutableDictionary* d = [[burn properties] mutableCopy];
|
||||
[d autorelease];
|
||||
[d setObject: [NSNumber numberWithBool: peformFakeBurnForTesting] forKey: DRBurnTestingKey];
|
||||
[d setObject: [NSNumber numberWithBool: false] forKey: DRBurnVerifyDiscKey];
|
||||
[d setObject: (shouldEject ? DRBurnCompletionActionEject : DRBurnCompletionActionMount) forKey: DRBurnCompletionActionKey];
|
||||
|
||||
if (burnSpeed > 0)
|
||||
[d setObject: [NSNumber numberWithFloat: burnSpeed * kilobytesPerSecond1x] forKey: DRBurnRequestedSpeedKey];
|
||||
|
||||
if (! underrunProtection)
|
||||
[d setObject: [NSNumber numberWithBool: false] forKey: DRBurnUnderrunProtectionKey];
|
||||
|
||||
[burn setProperties: d];
|
||||
|
||||
[burn writeLayout: tracks];
|
||||
|
||||
for (;;)
|
||||
{
|
||||
Thread::sleep (300);
|
||||
float progress = [[[burn status] objectForKey: DRStatusPercentCompleteKey] floatValue];
|
||||
|
||||
if (listener != nullptr && listener->audioCDBurnProgress (progress))
|
||||
{
|
||||
[burn abort];
|
||||
return "User cancelled the write operation";
|
||||
}
|
||||
|
||||
if ([[[burn status] objectForKey: DRStatusStateKey] isEqualTo: DRStatusStateFailed])
|
||||
return "Write operation failed";
|
||||
|
||||
if ([[[burn status] objectForKey: DRStatusStateKey] isEqualTo: DRStatusStateDone])
|
||||
break;
|
||||
|
||||
NSString* err = (NSString*) [[[burn status] objectForKey: DRErrorStatusKey]
|
||||
objectForKey: DRErrorStatusErrorStringKey];
|
||||
if ([err length] > 0)
|
||||
return nsStringToJuce (err);
|
||||
}
|
||||
|
||||
[device releaseMediaReservation];
|
||||
[device releaseExclusiveAccess];
|
||||
return {};
|
||||
}
|
||||
|
||||
DRDevice* device;
|
||||
NSMutableArray* tracks;
|
||||
bool underrunProtection;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class AudioCDBurner::Pimpl : private Timer
|
||||
{
|
||||
public:
|
||||
Pimpl (AudioCDBurner& b, int deviceIndex) : owner (b)
|
||||
{
|
||||
if (DRDevice* dev = [[DRDevice devices] objectAtIndex: static_cast<NSUInteger> (deviceIndex)])
|
||||
{
|
||||
device.reset (new OpenDiskDevice (dev));
|
||||
lastState = getDiskState();
|
||||
startTimer (1000);
|
||||
}
|
||||
}
|
||||
|
||||
~Pimpl() override
|
||||
{
|
||||
stopTimer();
|
||||
}
|
||||
|
||||
DiskState getDiskState() const
|
||||
{
|
||||
if ([device->device isValid])
|
||||
{
|
||||
NSDictionary* status = [device->device status];
|
||||
NSString* state = [status objectForKey: DRDeviceMediaStateKey];
|
||||
|
||||
if ([state isEqualTo: DRDeviceMediaStateNone])
|
||||
{
|
||||
if ([[status objectForKey: DRDeviceIsTrayOpenKey] boolValue])
|
||||
return trayOpen;
|
||||
|
||||
return noDisc;
|
||||
}
|
||||
|
||||
if ([state isEqualTo: DRDeviceMediaStateMediaPresent])
|
||||
{
|
||||
if ([[[status objectForKey: DRDeviceMediaInfoKey] objectForKey: DRDeviceMediaBlocksFreeKey] intValue] > 0)
|
||||
return writableDiskPresent;
|
||||
|
||||
return readOnlyDiskPresent;
|
||||
}
|
||||
}
|
||||
|
||||
return unknown;
|
||||
}
|
||||
|
||||
bool openTray() { return [device->device isValid] && [device->device ejectMedia]; }
|
||||
|
||||
Array<int> getAvailableWriteSpeeds() const
|
||||
{
|
||||
Array<int> results;
|
||||
|
||||
if ([device->device isValid])
|
||||
for (id kbPerSec in [[[device->device status] objectForKey: DRDeviceMediaInfoKey] objectForKey: DRDeviceBurnSpeedsKey])
|
||||
results.add ([kbPerSec intValue] / kilobytesPerSecond1x);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
bool setBufferUnderrunProtection (const bool shouldBeEnabled)
|
||||
{
|
||||
if ([device->device isValid])
|
||||
{
|
||||
device->underrunProtection = shouldBeEnabled;
|
||||
return shouldBeEnabled && [[[device->device status] objectForKey: DRDeviceCanUnderrunProtectCDKey] boolValue];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int getNumAvailableAudioBlocks() const
|
||||
{
|
||||
return [[[[device->device status] objectForKey: DRDeviceMediaInfoKey]
|
||||
objectForKey: DRDeviceMediaBlocksFreeKey] intValue];
|
||||
}
|
||||
|
||||
std::unique_ptr<OpenDiskDevice> device;
|
||||
|
||||
private:
|
||||
void timerCallback() override
|
||||
{
|
||||
const DiskState state = getDiskState();
|
||||
|
||||
if (state != lastState)
|
||||
{
|
||||
lastState = state;
|
||||
owner.sendChangeMessage();
|
||||
}
|
||||
}
|
||||
|
||||
DiskState lastState;
|
||||
AudioCDBurner& owner;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
AudioCDBurner::AudioCDBurner (const int deviceIndex)
|
||||
{
|
||||
pimpl.reset (new Pimpl (*this, deviceIndex));
|
||||
}
|
||||
|
||||
AudioCDBurner::~AudioCDBurner()
|
||||
{
|
||||
}
|
||||
|
||||
AudioCDBurner* AudioCDBurner::openDevice (const int deviceIndex)
|
||||
{
|
||||
std::unique_ptr<AudioCDBurner> b (new AudioCDBurner (deviceIndex));
|
||||
|
||||
if (b->pimpl->device == nil)
|
||||
b = nullptr;
|
||||
|
||||
return b.release();
|
||||
}
|
||||
|
||||
StringArray AudioCDBurner::findAvailableDevices()
|
||||
{
|
||||
StringArray s;
|
||||
|
||||
for (NSDictionary* dic in [DRDevice devices])
|
||||
if (NSString* name = [dic valueForKey: DRDeviceProductNameKey])
|
||||
s.add (nsStringToJuce (name));
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
AudioCDBurner::DiskState AudioCDBurner::getDiskState() const
|
||||
{
|
||||
return pimpl->getDiskState();
|
||||
}
|
||||
|
||||
bool AudioCDBurner::isDiskPresent() const
|
||||
{
|
||||
return getDiskState() == writableDiskPresent;
|
||||
}
|
||||
|
||||
bool AudioCDBurner::openTray()
|
||||
{
|
||||
return pimpl->openTray();
|
||||
}
|
||||
|
||||
AudioCDBurner::DiskState AudioCDBurner::waitUntilStateChange (int timeOutMilliseconds)
|
||||
{
|
||||
const int64 timeout = Time::currentTimeMillis() + timeOutMilliseconds;
|
||||
DiskState oldState = getDiskState();
|
||||
DiskState newState = oldState;
|
||||
|
||||
while (newState == oldState && Time::currentTimeMillis() < timeout)
|
||||
{
|
||||
newState = getDiskState();
|
||||
Thread::sleep (100);
|
||||
}
|
||||
|
||||
return newState;
|
||||
}
|
||||
|
||||
Array<int> AudioCDBurner::getAvailableWriteSpeeds() const
|
||||
{
|
||||
return pimpl->getAvailableWriteSpeeds();
|
||||
}
|
||||
|
||||
bool AudioCDBurner::setBufferUnderrunProtection (const bool shouldBeEnabled)
|
||||
{
|
||||
return pimpl->setBufferUnderrunProtection (shouldBeEnabled);
|
||||
}
|
||||
|
||||
int AudioCDBurner::getNumAvailableAudioBlocks() const
|
||||
{
|
||||
return pimpl->getNumAvailableAudioBlocks();
|
||||
}
|
||||
|
||||
bool AudioCDBurner::addAudioTrack (AudioSource* source, int numSamps)
|
||||
{
|
||||
if ([pimpl->device->device isValid])
|
||||
{
|
||||
pimpl->device->addSourceTrack (source, numSamps);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
String AudioCDBurner::burn (AudioCDBurner::BurnProgressListener* listener,
|
||||
bool ejectDiscAfterwards,
|
||||
bool performFakeBurnForTesting,
|
||||
int writeSpeed)
|
||||
{
|
||||
if ([pimpl->device->device isValid])
|
||||
return pimpl->device->burn (listener, ejectDiscAfterwards, performFakeBurnForTesting, writeSpeed);
|
||||
|
||||
return "Couldn't open or write to the CD device";
|
||||
}
|
||||
|
||||
}
|
266
deps/juce/modules/juce_audio_utils/native/juce_mac_AudioCDReader.mm
vendored
Normal file
266
deps/juce/modules/juce_audio_utils/native/juce_mac_AudioCDReader.mm
vendored
Normal file
@ -0,0 +1,266 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
namespace CDReaderHelpers
|
||||
{
|
||||
inline const XmlElement* getElementForKey (const XmlElement& xml, const String& key)
|
||||
{
|
||||
for (auto* child : xml.getChildWithTagNameIterator ("key"))
|
||||
if (child->getAllSubText().trim() == key)
|
||||
return child->getNextElement();
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static int getIntValueForKey (const XmlElement& xml, const String& key, int defaultValue = -1)
|
||||
{
|
||||
const XmlElement* const block = getElementForKey (xml, key);
|
||||
return block != nullptr ? block->getAllSubText().trim().getIntValue() : defaultValue;
|
||||
}
|
||||
|
||||
// Get the track offsets for a CD given an XmlElement representing its TOC.Plist.
|
||||
// Returns NULL on success, otherwise a const char* representing an error.
|
||||
static const char* getTrackOffsets (XmlDocument& xmlDocument, Array<int>& offsets)
|
||||
{
|
||||
const std::unique_ptr<XmlElement> xml (xmlDocument.getDocumentElement());
|
||||
if (xml == nullptr)
|
||||
return "Couldn't parse XML in file";
|
||||
|
||||
const XmlElement* const dict = xml->getChildByName ("dict");
|
||||
if (dict == nullptr)
|
||||
return "Couldn't get top level dictionary";
|
||||
|
||||
const XmlElement* const sessions = getElementForKey (*dict, "Sessions");
|
||||
if (sessions == nullptr)
|
||||
return "Couldn't find sessions key";
|
||||
|
||||
const XmlElement* const session = sessions->getFirstChildElement();
|
||||
if (session == nullptr)
|
||||
return "Couldn't find first session";
|
||||
|
||||
const int leadOut = getIntValueForKey (*session, "Leadout Block");
|
||||
if (leadOut < 0)
|
||||
return "Couldn't find Leadout Block";
|
||||
|
||||
const XmlElement* const trackArray = getElementForKey (*session, "Track Array");
|
||||
if (trackArray == nullptr)
|
||||
return "Couldn't find Track Array";
|
||||
|
||||
for (auto* track : trackArray->getChildIterator())
|
||||
{
|
||||
const int trackValue = getIntValueForKey (*track, "Start Block");
|
||||
if (trackValue < 0)
|
||||
return "Couldn't find Start Block in the track";
|
||||
|
||||
offsets.add (trackValue * AudioCDReader::samplesPerFrame - 88200);
|
||||
}
|
||||
|
||||
offsets.add (leadOut * AudioCDReader::samplesPerFrame - 88200);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void findDevices (Array<File>& cds)
|
||||
{
|
||||
File volumes ("/Volumes");
|
||||
volumes.findChildFiles (cds, File::findDirectories, false);
|
||||
|
||||
for (int i = cds.size(); --i >= 0;)
|
||||
if (! cds.getReference(i).getChildFile (".TOC.plist").exists())
|
||||
cds.remove (i);
|
||||
}
|
||||
|
||||
struct TrackSorter
|
||||
{
|
||||
static int getCDTrackNumber (const File& file)
|
||||
{
|
||||
return file.getFileName().initialSectionContainingOnly ("0123456789").getIntValue();
|
||||
}
|
||||
|
||||
static int compareElements (const File& first, const File& second)
|
||||
{
|
||||
const int firstTrack = getCDTrackNumber (first);
|
||||
const int secondTrack = getCDTrackNumber (second);
|
||||
|
||||
jassert (firstTrack > 0 && secondTrack > 0);
|
||||
|
||||
return firstTrack - secondTrack;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
StringArray AudioCDReader::getAvailableCDNames()
|
||||
{
|
||||
Array<File> cds;
|
||||
CDReaderHelpers::findDevices (cds);
|
||||
|
||||
StringArray names;
|
||||
|
||||
for (int i = 0; i < cds.size(); ++i)
|
||||
names.add (cds.getReference(i).getFileName());
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
AudioCDReader* AudioCDReader::createReaderForCD (const int index)
|
||||
{
|
||||
Array<File> cds;
|
||||
CDReaderHelpers::findDevices (cds);
|
||||
|
||||
if (cds[index].exists())
|
||||
return new AudioCDReader (cds[index]);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AudioCDReader::AudioCDReader (const File& volume)
|
||||
: AudioFormatReader (nullptr, "CD Audio"),
|
||||
volumeDir (volume),
|
||||
currentReaderTrack (-1)
|
||||
{
|
||||
sampleRate = 44100.0;
|
||||
bitsPerSample = 16;
|
||||
numChannels = 2;
|
||||
usesFloatingPointData = false;
|
||||
|
||||
refreshTrackLengths();
|
||||
}
|
||||
|
||||
AudioCDReader::~AudioCDReader()
|
||||
{
|
||||
}
|
||||
|
||||
void AudioCDReader::refreshTrackLengths()
|
||||
{
|
||||
tracks.clear();
|
||||
trackStartSamples.clear();
|
||||
lengthInSamples = 0;
|
||||
|
||||
volumeDir.findChildFiles (tracks, File::findFiles | File::ignoreHiddenFiles, false, "*.aiff");
|
||||
|
||||
CDReaderHelpers::TrackSorter sorter;
|
||||
tracks.sort (sorter);
|
||||
|
||||
const File toc (volumeDir.getChildFile (".TOC.plist"));
|
||||
|
||||
if (toc.exists())
|
||||
{
|
||||
XmlDocument doc (toc);
|
||||
const char* error = CDReaderHelpers::getTrackOffsets (doc, trackStartSamples);
|
||||
ignoreUnused (error); // could be logged..
|
||||
|
||||
lengthInSamples = trackStartSamples.getLast() - trackStartSamples.getFirst();
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioCDReader::readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer,
|
||||
int64 startSampleInFile, int numSamples)
|
||||
{
|
||||
while (numSamples > 0)
|
||||
{
|
||||
int track = -1;
|
||||
|
||||
for (int i = 0; i < trackStartSamples.size() - 1; ++i)
|
||||
{
|
||||
if (startSampleInFile < trackStartSamples.getUnchecked (i + 1))
|
||||
{
|
||||
track = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (track < 0)
|
||||
return false;
|
||||
|
||||
if (track != currentReaderTrack)
|
||||
{
|
||||
reader = nullptr;
|
||||
|
||||
if (auto in = tracks [track].createInputStream())
|
||||
{
|
||||
BufferedInputStream* const bin = new BufferedInputStream (in.release(), 65536, true);
|
||||
|
||||
AiffAudioFormat format;
|
||||
reader.reset (format.createReaderFor (bin, true));
|
||||
|
||||
if (reader == nullptr)
|
||||
currentReaderTrack = -1;
|
||||
else
|
||||
currentReaderTrack = track;
|
||||
}
|
||||
}
|
||||
|
||||
if (reader == nullptr)
|
||||
return false;
|
||||
|
||||
const int startPos = (int) (startSampleInFile - trackStartSamples.getUnchecked (track));
|
||||
const int numAvailable = (int) jmin ((int64) numSamples, reader->lengthInSamples - startPos);
|
||||
|
||||
reader->readSamples (destSamples, numDestChannels, startOffsetInDestBuffer, startPos, numAvailable);
|
||||
|
||||
numSamples -= numAvailable;
|
||||
startSampleInFile += numAvailable;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioCDReader::isCDStillPresent() const
|
||||
{
|
||||
return volumeDir.exists();
|
||||
}
|
||||
|
||||
void AudioCDReader::ejectDisk()
|
||||
{
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
[[NSWorkspace sharedWorkspace] unmountAndEjectDeviceAtPath: juceStringToNS (volumeDir.getFullPathName())];
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioCDReader::isTrackAudio (int trackNum) const
|
||||
{
|
||||
return tracks [trackNum].hasFileExtension (".aiff");
|
||||
}
|
||||
|
||||
void AudioCDReader::enableIndexScanning (bool)
|
||||
{
|
||||
// any way to do this on a Mac??
|
||||
}
|
||||
|
||||
int AudioCDReader::getLastIndex() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
Array<int> AudioCDReader::findIndexesInTrack (const int /*trackNumber*/)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace juce
|
191
deps/juce/modules/juce_audio_utils/native/juce_mac_BluetoothMidiDevicePairingDialogue.mm
vendored
Normal file
191
deps/juce/modules/juce_audio_utils/native/juce_mac_BluetoothMidiDevicePairingDialogue.mm
vendored
Normal file
@ -0,0 +1,191 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#if defined (MAC_OS_X_VERSION_10_11) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11
|
||||
|
||||
//==============================================================================
|
||||
class BluetoothMidiPairingWindowClass : public ObjCClass<NSObject>
|
||||
{
|
||||
public:
|
||||
struct Callbacks
|
||||
{
|
||||
std::unique_ptr<ModalComponentManager::Callback> modalExit;
|
||||
std::function<void()> windowClosed;
|
||||
};
|
||||
|
||||
BluetoothMidiPairingWindowClass() : ObjCClass<NSObject> ("JUCEBluetoothMidiPairingWindowClass_")
|
||||
{
|
||||
addIvar<Callbacks*> ("callbacks");
|
||||
addIvar<CABTLEMIDIWindowController*> ("controller");
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
addMethod (@selector (initWithCallbacks:), initWithCallbacks, "@@:^v");
|
||||
addMethod (@selector (show:), show, "v@:^v");
|
||||
addMethod (@selector (receivedWindowWillClose:), receivedWindowWillClose, "v@:^v");
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
addMethod (@selector (dealloc), dealloc, "v@:");
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
private:
|
||||
static CABTLEMIDIWindowController* getController (id self)
|
||||
{
|
||||
return getIvar<CABTLEMIDIWindowController*> (self, "controller");
|
||||
}
|
||||
|
||||
static id initWithCallbacks (id self, SEL, Callbacks* cbs)
|
||||
{
|
||||
self = sendSuperclassMessage<id> (self, @selector (init));
|
||||
|
||||
object_setInstanceVariable (self, "callbacks", cbs);
|
||||
object_setInstanceVariable (self, "controller", [CABTLEMIDIWindowController new]);
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
[[NSNotificationCenter defaultCenter] addObserver: self
|
||||
selector: @selector (receivedWindowWillClose:)
|
||||
name: @"NSWindowWillCloseNotification"
|
||||
object: [getController (self) window]];
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
static void dealloc (id self, SEL)
|
||||
{
|
||||
[getController (self) release];
|
||||
sendSuperclassMessage<void> (self, @selector (dealloc));
|
||||
}
|
||||
|
||||
static void show (id self, SEL, Rectangle<int>* bounds)
|
||||
{
|
||||
if (bounds != nullptr)
|
||||
{
|
||||
auto nsBounds = makeNSRect (*bounds);
|
||||
|
||||
auto mainScreenHeight = []
|
||||
{
|
||||
if ([[NSScreen screens] count] == 0)
|
||||
return (CGFloat) 0.0f;
|
||||
|
||||
return [[[NSScreen screens] objectAtIndex: 0] frame].size.height;
|
||||
}();
|
||||
|
||||
nsBounds.origin.y = mainScreenHeight - (nsBounds.origin.y + nsBounds.size.height);
|
||||
|
||||
[getController (self).window setFrame: nsBounds
|
||||
display: YES];
|
||||
}
|
||||
|
||||
[getController (self) showWindow: nil];
|
||||
}
|
||||
|
||||
static void receivedWindowWillClose (id self, SEL, NSNotification*)
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver: self];
|
||||
|
||||
auto* cbs = getIvar<Callbacks*> (self, "callbacks");
|
||||
|
||||
if (cbs->modalExit != nullptr)
|
||||
cbs->modalExit->modalStateFinished (0);
|
||||
|
||||
cbs->windowClosed();
|
||||
}
|
||||
};
|
||||
|
||||
class BluetoothMidiSelectorWindowHelper : public DeletedAtShutdown
|
||||
{
|
||||
public:
|
||||
BluetoothMidiSelectorWindowHelper (ModalComponentManager::Callback* exitCallback,
|
||||
Rectangle<int>* bounds)
|
||||
{
|
||||
std::unique_ptr<ModalComponentManager::Callback> exitCB (exitCallback);
|
||||
|
||||
static BluetoothMidiPairingWindowClass cls;
|
||||
window.reset (cls.createInstance());
|
||||
|
||||
auto deletionCB = [safeThis = WeakReference<BluetoothMidiSelectorWindowHelper> { this }]
|
||||
{
|
||||
if (safeThis != nullptr)
|
||||
delete safeThis.get();
|
||||
};
|
||||
|
||||
callbacks.reset (new BluetoothMidiPairingWindowClass::Callbacks { std::move (exitCB),
|
||||
std::move (deletionCB) });
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
[window.get() performSelector: @selector (initWithCallbacks:)
|
||||
withObject: (id) callbacks.get()];
|
||||
[window.get() performSelector: @selector (show:)
|
||||
withObject: (id) bounds];
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<NSObject, NSObjectDeleter> window;
|
||||
std::unique_ptr<BluetoothMidiPairingWindowClass::Callbacks> callbacks;
|
||||
|
||||
JUCE_DECLARE_WEAK_REFERENCEABLE (BluetoothMidiSelectorWindowHelper)
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BluetoothMidiSelectorWindowHelper)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
bool BluetoothMidiDevicePairingDialogue::open (ModalComponentManager::Callback* exitCallback,
|
||||
Rectangle<int>* bounds)
|
||||
{
|
||||
new BluetoothMidiSelectorWindowHelper (exitCallback, bounds);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BluetoothMidiDevicePairingDialogue::isAvailable()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
bool BluetoothMidiDevicePairingDialogue::open (ModalComponentManager::Callback* exitCallback,
|
||||
Rectangle<int>*)
|
||||
{
|
||||
std::unique_ptr<ModalComponentManager::Callback> cb (exitCallback);
|
||||
// This functionality is unavailable when targetting OSX < 10.11. Instead,
|
||||
// you should pair Bluetooth MIDI devices using the "Audio MIDI Setup" app
|
||||
// (located in /Applications/Utilities).
|
||||
jassertfalse;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BluetoothMidiDevicePairingDialogue::isAvailable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
417
deps/juce/modules/juce_audio_utils/native/juce_win32_AudioCDBurner.cpp
vendored
Normal file
417
deps/juce/modules/juce_audio_utils/native/juce_win32_AudioCDBurner.cpp
vendored
Normal file
@ -0,0 +1,417 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
namespace CDBurnerHelpers
|
||||
{
|
||||
IDiscRecorder* enumCDBurners (StringArray* list, int indexToOpen, IDiscMaster** master)
|
||||
{
|
||||
CoInitialize (0);
|
||||
|
||||
IDiscMaster* dm;
|
||||
IDiscRecorder* result = nullptr;
|
||||
|
||||
if (SUCCEEDED (CoCreateInstance (CLSID_MSDiscMasterObj, 0,
|
||||
CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER,
|
||||
IID_IDiscMaster,
|
||||
(void**) &dm)))
|
||||
{
|
||||
if (SUCCEEDED (dm->Open()))
|
||||
{
|
||||
IEnumDiscRecorders* drEnum = nullptr;
|
||||
|
||||
if (SUCCEEDED (dm->EnumDiscRecorders (&drEnum)))
|
||||
{
|
||||
IDiscRecorder* dr = nullptr;
|
||||
DWORD dummy;
|
||||
int index = 0;
|
||||
|
||||
while (drEnum->Next (1, &dr, &dummy) == S_OK)
|
||||
{
|
||||
if (indexToOpen == index)
|
||||
{
|
||||
result = dr;
|
||||
break;
|
||||
}
|
||||
else if (list != nullptr)
|
||||
{
|
||||
BSTR path;
|
||||
|
||||
if (SUCCEEDED (dr->GetPath (&path)))
|
||||
list->add ((const WCHAR*) path);
|
||||
}
|
||||
|
||||
++index;
|
||||
dr->Release();
|
||||
}
|
||||
|
||||
drEnum->Release();
|
||||
}
|
||||
|
||||
if (master == 0)
|
||||
dm->Close();
|
||||
}
|
||||
|
||||
if (master != nullptr)
|
||||
*master = dm;
|
||||
else
|
||||
dm->Release();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class AudioCDBurner::Pimpl : public ComBaseClassHelper <IDiscMasterProgressEvents>,
|
||||
public Timer
|
||||
{
|
||||
public:
|
||||
Pimpl (AudioCDBurner& owner_, IDiscMaster* discMaster_, IDiscRecorder* discRecorder_)
|
||||
: owner (owner_), discMaster (discMaster_), discRecorder (discRecorder_), redbook (0),
|
||||
listener (0), progress (0), shouldCancel (false)
|
||||
{
|
||||
HRESULT hr = discMaster->SetActiveDiscMasterFormat (IID_IRedbookDiscMaster, (void**) &redbook);
|
||||
jassert (SUCCEEDED (hr));
|
||||
hr = discMaster->SetActiveDiscRecorder (discRecorder);
|
||||
//jassert (SUCCEEDED (hr));
|
||||
|
||||
lastState = getDiskState();
|
||||
startTimer (2000);
|
||||
}
|
||||
|
||||
~Pimpl() {}
|
||||
|
||||
void releaseObjects()
|
||||
{
|
||||
discRecorder->Close();
|
||||
if (redbook != nullptr)
|
||||
redbook->Release();
|
||||
discRecorder->Release();
|
||||
discMaster->Release();
|
||||
Release();
|
||||
}
|
||||
|
||||
JUCE_COMRESULT QueryCancel (boolean* pbCancel)
|
||||
{
|
||||
if (listener != nullptr && ! shouldCancel)
|
||||
shouldCancel = listener->audioCDBurnProgress (progress);
|
||||
|
||||
*pbCancel = shouldCancel;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT NotifyBlockProgress (long nCompleted, long nTotal)
|
||||
{
|
||||
progress = nCompleted / (float) nTotal;
|
||||
shouldCancel = listener != nullptr && listener->audioCDBurnProgress (progress);
|
||||
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT NotifyPnPActivity (void) { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT NotifyAddProgress (long /*nCompletedSteps*/, long /*nTotalSteps*/) { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT NotifyTrackProgress (long /*nCurrentTrack*/, long /*nTotalTracks*/) { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT NotifyPreparingBurn (long /*nEstimatedSeconds*/) { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT NotifyClosingDisc (long /*nEstimatedSeconds*/) { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT NotifyBurnComplete (HRESULT /*status*/) { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT NotifyEraseComplete (HRESULT /*status*/) { return E_NOTIMPL; }
|
||||
|
||||
class ScopedDiscOpener
|
||||
{
|
||||
public:
|
||||
ScopedDiscOpener (Pimpl& p) : pimpl (p) { pimpl.discRecorder->OpenExclusive(); }
|
||||
~ScopedDiscOpener() { pimpl.discRecorder->Close(); }
|
||||
|
||||
private:
|
||||
Pimpl& pimpl;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (ScopedDiscOpener)
|
||||
};
|
||||
|
||||
DiskState getDiskState()
|
||||
{
|
||||
const ScopedDiscOpener opener (*this);
|
||||
|
||||
long type, flags;
|
||||
HRESULT hr = discRecorder->QueryMediaType (&type, &flags);
|
||||
|
||||
if (FAILED (hr))
|
||||
return unknown;
|
||||
|
||||
if (type != 0 && (flags & MEDIA_WRITABLE) != 0)
|
||||
return writableDiskPresent;
|
||||
|
||||
if (type == 0)
|
||||
return noDisc;
|
||||
|
||||
return readOnlyDiskPresent;
|
||||
}
|
||||
|
||||
int getIntProperty (const LPOLESTR name, const int defaultReturn) const
|
||||
{
|
||||
ComSmartPtr<IPropertyStorage> prop;
|
||||
if (FAILED (discRecorder->GetRecorderProperties (prop.resetAndGetPointerAddress())))
|
||||
return defaultReturn;
|
||||
|
||||
PROPSPEC iPropSpec;
|
||||
iPropSpec.ulKind = PRSPEC_LPWSTR;
|
||||
iPropSpec.lpwstr = name;
|
||||
|
||||
PROPVARIANT iPropVariant;
|
||||
return FAILED (prop->ReadMultiple (1, &iPropSpec, &iPropVariant))
|
||||
? defaultReturn : (int) iPropVariant.lVal;
|
||||
}
|
||||
|
||||
bool setIntProperty (const LPOLESTR name, const int value) const
|
||||
{
|
||||
ComSmartPtr<IPropertyStorage> prop;
|
||||
if (FAILED (discRecorder->GetRecorderProperties (prop.resetAndGetPointerAddress())))
|
||||
return false;
|
||||
|
||||
PROPSPEC iPropSpec;
|
||||
iPropSpec.ulKind = PRSPEC_LPWSTR;
|
||||
iPropSpec.lpwstr = name;
|
||||
|
||||
PROPVARIANT iPropVariant;
|
||||
if (FAILED (prop->ReadMultiple (1, &iPropSpec, &iPropVariant)))
|
||||
return false;
|
||||
|
||||
iPropVariant.lVal = (long) value;
|
||||
return SUCCEEDED (prop->WriteMultiple (1, &iPropSpec, &iPropVariant, iPropVariant.vt))
|
||||
&& SUCCEEDED (discRecorder->SetRecorderProperties (prop));
|
||||
}
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
const DiskState state = getDiskState();
|
||||
|
||||
if (state != lastState)
|
||||
{
|
||||
lastState = state;
|
||||
owner.sendChangeMessage();
|
||||
}
|
||||
}
|
||||
|
||||
AudioCDBurner& owner;
|
||||
DiskState lastState;
|
||||
IDiscMaster* discMaster;
|
||||
IDiscRecorder* discRecorder;
|
||||
IRedbookDiscMaster* redbook;
|
||||
AudioCDBurner::BurnProgressListener* listener;
|
||||
float progress;
|
||||
bool shouldCancel;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
AudioCDBurner::AudioCDBurner (const int deviceIndex)
|
||||
{
|
||||
IDiscMaster* discMaster = nullptr;
|
||||
IDiscRecorder* discRecorder = CDBurnerHelpers::enumCDBurners (0, deviceIndex, &discMaster);
|
||||
|
||||
if (discRecorder != nullptr)
|
||||
pimpl.reset (new Pimpl (*this, discMaster, discRecorder));
|
||||
}
|
||||
|
||||
AudioCDBurner::~AudioCDBurner()
|
||||
{
|
||||
if (pimpl != nullptr)
|
||||
pimpl.release()->releaseObjects();
|
||||
}
|
||||
|
||||
StringArray AudioCDBurner::findAvailableDevices()
|
||||
{
|
||||
StringArray devs;
|
||||
CDBurnerHelpers::enumCDBurners (&devs, -1, 0);
|
||||
return devs;
|
||||
}
|
||||
|
||||
AudioCDBurner* AudioCDBurner::openDevice (const int deviceIndex)
|
||||
{
|
||||
std::unique_ptr<AudioCDBurner> b (new AudioCDBurner (deviceIndex));
|
||||
|
||||
if (b->pimpl == 0)
|
||||
b = nullptr;
|
||||
|
||||
return b.release();
|
||||
}
|
||||
|
||||
AudioCDBurner::DiskState AudioCDBurner::getDiskState() const
|
||||
{
|
||||
return pimpl->getDiskState();
|
||||
}
|
||||
|
||||
bool AudioCDBurner::isDiskPresent() const
|
||||
{
|
||||
return getDiskState() == writableDiskPresent;
|
||||
}
|
||||
|
||||
bool AudioCDBurner::openTray()
|
||||
{
|
||||
const Pimpl::ScopedDiscOpener opener (*pimpl);
|
||||
return SUCCEEDED (pimpl->discRecorder->Eject());
|
||||
}
|
||||
|
||||
AudioCDBurner::DiskState AudioCDBurner::waitUntilStateChange (int timeOutMilliseconds)
|
||||
{
|
||||
const int64 timeout = Time::currentTimeMillis() + timeOutMilliseconds;
|
||||
DiskState oldState = getDiskState();
|
||||
DiskState newState = oldState;
|
||||
|
||||
while (newState == oldState && Time::currentTimeMillis() < timeout)
|
||||
{
|
||||
newState = getDiskState();
|
||||
Thread::sleep (jmin (250, (int) (timeout - Time::currentTimeMillis())));
|
||||
}
|
||||
|
||||
return newState;
|
||||
}
|
||||
|
||||
Array<int> AudioCDBurner::getAvailableWriteSpeeds() const
|
||||
{
|
||||
Array<int> results;
|
||||
const int maxSpeed = pimpl->getIntProperty (L"MaxWriteSpeed", 1);
|
||||
const int speeds[] = { 1, 2, 4, 8, 12, 16, 20, 24, 32, 40, 64, 80 };
|
||||
|
||||
for (int i = 0; i < numElementsInArray (speeds); ++i)
|
||||
if (speeds[i] <= maxSpeed)
|
||||
results.add (speeds[i]);
|
||||
|
||||
results.addIfNotAlreadyThere (maxSpeed);
|
||||
return results;
|
||||
}
|
||||
|
||||
bool AudioCDBurner::setBufferUnderrunProtection (const bool shouldBeEnabled)
|
||||
{
|
||||
if (pimpl->getIntProperty (L"BufferUnderrunFreeCapable", 0) == 0)
|
||||
return false;
|
||||
|
||||
pimpl->setIntProperty (L"EnableBufferUnderrunFree", shouldBeEnabled ? -1 : 0);
|
||||
return pimpl->getIntProperty (L"EnableBufferUnderrunFree", 0) != 0;
|
||||
}
|
||||
|
||||
int AudioCDBurner::getNumAvailableAudioBlocks() const
|
||||
{
|
||||
long blocksFree = 0;
|
||||
pimpl->redbook->GetAvailableAudioTrackBlocks (&blocksFree);
|
||||
return blocksFree;
|
||||
}
|
||||
|
||||
String AudioCDBurner::burn (AudioCDBurner::BurnProgressListener* listener, bool ejectDiscAfterwards,
|
||||
bool performFakeBurnForTesting, int writeSpeed)
|
||||
{
|
||||
pimpl->setIntProperty (L"WriteSpeed", writeSpeed > 0 ? writeSpeed : -1);
|
||||
|
||||
pimpl->listener = listener;
|
||||
pimpl->progress = 0;
|
||||
pimpl->shouldCancel = false;
|
||||
|
||||
UINT_PTR cookie;
|
||||
HRESULT hr = pimpl->discMaster->ProgressAdvise ((AudioCDBurner::Pimpl*) pimpl.get(), &cookie);
|
||||
|
||||
hr = pimpl->discMaster->RecordDisc (performFakeBurnForTesting,
|
||||
ejectDiscAfterwards);
|
||||
|
||||
String error;
|
||||
if (hr != S_OK)
|
||||
{
|
||||
const char* e = "Couldn't open or write to the CD device";
|
||||
|
||||
if (hr == IMAPI_E_USERABORT)
|
||||
e = "User cancelled the write operation";
|
||||
else if (hr == IMAPI_E_MEDIUM_NOTPRESENT || hr == IMAPI_E_TRACKOPEN)
|
||||
e = "No Disk present";
|
||||
|
||||
error = e;
|
||||
}
|
||||
|
||||
pimpl->discMaster->ProgressUnadvise (cookie);
|
||||
pimpl->listener = 0;
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
bool AudioCDBurner::addAudioTrack (AudioSource* audioSource, int numSamples)
|
||||
{
|
||||
if (audioSource == 0)
|
||||
return false;
|
||||
|
||||
std::unique_ptr<AudioSource> source (audioSource);
|
||||
|
||||
long bytesPerBlock;
|
||||
HRESULT hr = pimpl->redbook->GetAudioBlockSize (&bytesPerBlock);
|
||||
|
||||
const int samplesPerBlock = bytesPerBlock / 4;
|
||||
bool ok = true;
|
||||
|
||||
hr = pimpl->redbook->CreateAudioTrack ((long) numSamples / (bytesPerBlock * 4));
|
||||
|
||||
HeapBlock<byte> buffer (bytesPerBlock);
|
||||
AudioBuffer<float> sourceBuffer (2, samplesPerBlock);
|
||||
int samplesDone = 0;
|
||||
|
||||
source->prepareToPlay (samplesPerBlock, 44100.0);
|
||||
|
||||
while (ok)
|
||||
{
|
||||
{
|
||||
AudioSourceChannelInfo info (&sourceBuffer, 0, samplesPerBlock);
|
||||
sourceBuffer.clear();
|
||||
|
||||
source->getNextAudioBlock (info);
|
||||
}
|
||||
|
||||
buffer.clear (bytesPerBlock);
|
||||
|
||||
typedef AudioData::Pointer <AudioData::Int16, AudioData::LittleEndian,
|
||||
AudioData::Interleaved, AudioData::NonConst> CDSampleFormat;
|
||||
|
||||
typedef AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian,
|
||||
AudioData::NonInterleaved, AudioData::Const> SourceSampleFormat;
|
||||
|
||||
CDSampleFormat left (buffer, 2);
|
||||
left.convertSamples (SourceSampleFormat (sourceBuffer.getReadPointer (0)), samplesPerBlock);
|
||||
CDSampleFormat right (buffer + 2, 2);
|
||||
right.convertSamples (SourceSampleFormat (sourceBuffer.getReadPointer (1)), samplesPerBlock);
|
||||
|
||||
hr = pimpl->redbook->AddAudioTrackBlocks (buffer, bytesPerBlock);
|
||||
|
||||
if (FAILED (hr))
|
||||
ok = false;
|
||||
|
||||
samplesDone += samplesPerBlock;
|
||||
|
||||
if (samplesDone >= numSamples)
|
||||
break;
|
||||
}
|
||||
|
||||
hr = pimpl->redbook->CloseAudioTrack();
|
||||
return ok && hr == S_OK;
|
||||
}
|
||||
|
||||
} // namespace juce
|
1315
deps/juce/modules/juce_audio_utils/native/juce_win32_AudioCDReader.cpp
vendored
Normal file
1315
deps/juce/modules/juce_audio_utils/native/juce_win32_AudioCDReader.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
45
deps/juce/modules/juce_audio_utils/native/juce_win_BluetoothMidiDevicePairingDialogue.cpp
vendored
Normal file
45
deps/juce/modules/juce_audio_utils/native/juce_win_BluetoothMidiDevicePairingDialogue.cpp
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
bool BluetoothMidiDevicePairingDialogue::open (ModalComponentManager::Callback* exitCallback,
|
||||
Rectangle<int>*)
|
||||
{
|
||||
std::unique_ptr<ModalComponentManager::Callback> cb (exitCallback);
|
||||
// not implemented on Windows yet!
|
||||
// You should check whether the dialogue is available on your system
|
||||
// using isAvailable() before calling open().
|
||||
jassertfalse;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BluetoothMidiDevicePairingDialogue::isAvailable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace juce
|
436
deps/juce/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.cpp
vendored
Normal file
436
deps/juce/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.cpp
vendored
Normal file
@ -0,0 +1,436 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
template <typename Value>
|
||||
struct ChannelInfo
|
||||
{
|
||||
ChannelInfo() = default;
|
||||
ChannelInfo (Value** dataIn, int numChannelsIn)
|
||||
: data (dataIn), numChannels (numChannelsIn) {}
|
||||
|
||||
Value** data = nullptr;
|
||||
int numChannels = 0;
|
||||
};
|
||||
|
||||
/** Sets up `channels` so that it contains channel pointers suitable for passing to
|
||||
an AudioProcessor's processBlock.
|
||||
|
||||
On return, `channels` will hold `max (processorIns, processorOuts)` entries.
|
||||
The first `processorIns` entries will point to buffers holding input data.
|
||||
Any entries after the first `processorIns` entries will point to zeroed buffers.
|
||||
|
||||
In the case that the system only provides a single input channel, but the processor
|
||||
has been initialised with multiple input channels, the system input will be copied
|
||||
to all processor inputs.
|
||||
|
||||
In the case that the system provides no input channels, but the processor has
|
||||
been initialise with multiple input channels, the processor's input channels will
|
||||
all be zeroed.
|
||||
|
||||
@param ins the system inputs.
|
||||
@param outs the system outputs.
|
||||
@param numSamples the number of samples in the system buffers.
|
||||
@param processorIns the number of input channels requested by the processor.
|
||||
@param processorOuts the number of output channels requested by the processor.
|
||||
@param tempBuffer temporary storage for inputs that don't have a corresponding output.
|
||||
@param channels holds pointers to each of the processor's audio channels.
|
||||
*/
|
||||
static void initialiseIoBuffers (ChannelInfo<const float> ins,
|
||||
ChannelInfo<float> outs,
|
||||
const int numSamples,
|
||||
int processorIns,
|
||||
int processorOuts,
|
||||
AudioBuffer<float>& tempBuffer,
|
||||
std::vector<float*>& channels)
|
||||
{
|
||||
jassert ((int) channels.size() >= jmax (processorIns, processorOuts));
|
||||
|
||||
size_t totalNumChans = 0;
|
||||
const auto numBytes = (size_t) numSamples * sizeof (float);
|
||||
|
||||
const auto prepareInputChannel = [&] (int index)
|
||||
{
|
||||
if (ins.numChannels == 0)
|
||||
zeromem (channels[totalNumChans], numBytes);
|
||||
else
|
||||
memcpy (channels[totalNumChans], ins.data[index % ins.numChannels], numBytes);
|
||||
};
|
||||
|
||||
if (processorIns > processorOuts)
|
||||
{
|
||||
// If there aren't enough output channels for the number of
|
||||
// inputs, we need to use some temporary extra ones (can't
|
||||
// use the input data in case it gets written to).
|
||||
jassert (tempBuffer.getNumChannels() >= processorIns - processorOuts);
|
||||
jassert (tempBuffer.getNumSamples() >= numSamples);
|
||||
|
||||
for (int i = 0; i < processorOuts; ++i)
|
||||
{
|
||||
channels[totalNumChans] = outs.data[i];
|
||||
prepareInputChannel (i);
|
||||
++totalNumChans;
|
||||
}
|
||||
|
||||
for (auto i = processorOuts; i < processorIns; ++i)
|
||||
{
|
||||
channels[totalNumChans] = tempBuffer.getWritePointer (i - outs.numChannels);
|
||||
prepareInputChannel (i);
|
||||
++totalNumChans;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < processorIns; ++i)
|
||||
{
|
||||
channels[totalNumChans] = outs.data[i];
|
||||
prepareInputChannel (i);
|
||||
++totalNumChans;
|
||||
}
|
||||
|
||||
for (auto i = processorIns; i < processorOuts; ++i)
|
||||
{
|
||||
channels[totalNumChans] = outs.data[i];
|
||||
zeromem (channels[totalNumChans], (size_t) numSamples * sizeof (float));
|
||||
++totalNumChans;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
AudioProcessorPlayer::AudioProcessorPlayer (bool doDoublePrecisionProcessing)
|
||||
: isDoublePrecision (doDoublePrecisionProcessing)
|
||||
{
|
||||
}
|
||||
|
||||
AudioProcessorPlayer::~AudioProcessorPlayer()
|
||||
{
|
||||
setProcessor (nullptr);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
AudioProcessorPlayer::NumChannels AudioProcessorPlayer::findMostSuitableLayout (const AudioProcessor& proc) const
|
||||
{
|
||||
if (proc.isMidiEffect())
|
||||
return {};
|
||||
|
||||
std::vector<NumChannels> layouts { deviceChannels };
|
||||
|
||||
if (deviceChannels.ins == 0 || deviceChannels.ins == 1)
|
||||
{
|
||||
layouts.emplace_back (defaultProcessorChannels.ins, deviceChannels.outs);
|
||||
layouts.emplace_back (deviceChannels.outs, deviceChannels.outs);
|
||||
}
|
||||
|
||||
const auto it = std::find_if (layouts.begin(), layouts.end(), [&] (const NumChannels& chans)
|
||||
{
|
||||
return proc.checkBusesLayoutSupported (chans.toLayout());
|
||||
});
|
||||
|
||||
return it != std::end (layouts) ? *it : layouts[0];
|
||||
}
|
||||
|
||||
void AudioProcessorPlayer::resizeChannels()
|
||||
{
|
||||
const auto maxChannels = jmax (deviceChannels.ins,
|
||||
deviceChannels.outs,
|
||||
actualProcessorChannels.ins,
|
||||
actualProcessorChannels.outs);
|
||||
channels.resize ((size_t) maxChannels);
|
||||
tempBuffer.setSize (maxChannels, blockSize);
|
||||
}
|
||||
|
||||
void AudioProcessorPlayer::setProcessor (AudioProcessor* const processorToPlay)
|
||||
{
|
||||
const ScopedLock sl (lock);
|
||||
|
||||
if (processor == processorToPlay)
|
||||
return;
|
||||
|
||||
if (processorToPlay != nullptr && sampleRate > 0 && blockSize > 0)
|
||||
{
|
||||
defaultProcessorChannels = NumChannels { processorToPlay->getBusesLayout() };
|
||||
actualProcessorChannels = findMostSuitableLayout (*processorToPlay);
|
||||
|
||||
if (processorToPlay->isMidiEffect())
|
||||
processorToPlay->setRateAndBufferSizeDetails (sampleRate, blockSize);
|
||||
else
|
||||
processorToPlay->setPlayConfigDetails (actualProcessorChannels.ins,
|
||||
actualProcessorChannels.outs,
|
||||
sampleRate,
|
||||
blockSize);
|
||||
|
||||
auto supportsDouble = processorToPlay->supportsDoublePrecisionProcessing() && isDoublePrecision;
|
||||
|
||||
processorToPlay->setProcessingPrecision (supportsDouble ? AudioProcessor::doublePrecision
|
||||
: AudioProcessor::singlePrecision);
|
||||
processorToPlay->prepareToPlay (sampleRate, blockSize);
|
||||
}
|
||||
|
||||
AudioProcessor* oldOne = nullptr;
|
||||
|
||||
oldOne = isPrepared ? processor : nullptr;
|
||||
processor = processorToPlay;
|
||||
isPrepared = true;
|
||||
resizeChannels();
|
||||
|
||||
if (oldOne != nullptr)
|
||||
oldOne->releaseResources();
|
||||
}
|
||||
|
||||
void AudioProcessorPlayer::setDoublePrecisionProcessing (bool doublePrecision)
|
||||
{
|
||||
if (doublePrecision != isDoublePrecision)
|
||||
{
|
||||
const ScopedLock sl (lock);
|
||||
|
||||
if (processor != nullptr)
|
||||
{
|
||||
processor->releaseResources();
|
||||
|
||||
auto supportsDouble = processor->supportsDoublePrecisionProcessing() && doublePrecision;
|
||||
|
||||
processor->setProcessingPrecision (supportsDouble ? AudioProcessor::doublePrecision
|
||||
: AudioProcessor::singlePrecision);
|
||||
processor->prepareToPlay (sampleRate, blockSize);
|
||||
}
|
||||
|
||||
isDoublePrecision = doublePrecision;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioProcessorPlayer::setMidiOutput (MidiOutput* midiOutputToUse)
|
||||
{
|
||||
if (midiOutput != midiOutputToUse)
|
||||
{
|
||||
const ScopedLock sl (lock);
|
||||
midiOutput = midiOutputToUse;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void AudioProcessorPlayer::audioDeviceIOCallback (const float** const inputChannelData,
|
||||
const int numInputChannels,
|
||||
float** const outputChannelData,
|
||||
const int numOutputChannels,
|
||||
const int numSamples)
|
||||
{
|
||||
const ScopedLock sl (lock);
|
||||
|
||||
// These should have been prepared by audioDeviceAboutToStart()...
|
||||
jassert (sampleRate > 0 && blockSize > 0);
|
||||
|
||||
incomingMidi.clear();
|
||||
messageCollector.removeNextBlockOfMessages (incomingMidi, numSamples);
|
||||
|
||||
initialiseIoBuffers ({ inputChannelData, numInputChannels },
|
||||
{ outputChannelData, numOutputChannels },
|
||||
numSamples,
|
||||
actualProcessorChannels.ins,
|
||||
actualProcessorChannels.outs,
|
||||
tempBuffer,
|
||||
channels);
|
||||
|
||||
const auto totalNumChannels = jmax (actualProcessorChannels.ins, actualProcessorChannels.outs);
|
||||
AudioBuffer<float> buffer (channels.data(), (int) totalNumChannels, numSamples);
|
||||
|
||||
if (processor != nullptr)
|
||||
{
|
||||
// The processor should be prepared to deal with the same number of output channels
|
||||
// as our output device.
|
||||
jassert (processor->isMidiEffect() || numOutputChannels == actualProcessorChannels.outs);
|
||||
|
||||
const ScopedLock sl2 (processor->getCallbackLock());
|
||||
|
||||
if (! processor->isSuspended())
|
||||
{
|
||||
if (processor->isUsingDoublePrecision())
|
||||
{
|
||||
conversionBuffer.makeCopyOf (buffer, true);
|
||||
processor->processBlock (conversionBuffer, incomingMidi);
|
||||
buffer.makeCopyOf (conversionBuffer, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
processor->processBlock (buffer, incomingMidi);
|
||||
}
|
||||
|
||||
if (midiOutput != nullptr)
|
||||
{
|
||||
if (midiOutput->isBackgroundThreadRunning())
|
||||
{
|
||||
midiOutput->sendBlockOfMessages (incomingMidi,
|
||||
Time::getMillisecondCounterHiRes(),
|
||||
sampleRate);
|
||||
}
|
||||
else
|
||||
{
|
||||
midiOutput->sendBlockOfMessagesNow (incomingMidi);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < numOutputChannels; ++i)
|
||||
FloatVectorOperations::clear (outputChannelData[i], numSamples);
|
||||
}
|
||||
|
||||
void AudioProcessorPlayer::audioDeviceAboutToStart (AudioIODevice* const device)
|
||||
{
|
||||
auto newSampleRate = device->getCurrentSampleRate();
|
||||
auto newBlockSize = device->getCurrentBufferSizeSamples();
|
||||
auto numChansIn = device->getActiveInputChannels().countNumberOfSetBits();
|
||||
auto numChansOut = device->getActiveOutputChannels().countNumberOfSetBits();
|
||||
|
||||
const ScopedLock sl (lock);
|
||||
|
||||
sampleRate = newSampleRate;
|
||||
blockSize = newBlockSize;
|
||||
deviceChannels = { numChansIn, numChansOut };
|
||||
|
||||
resizeChannels();
|
||||
|
||||
messageCollector.reset (sampleRate);
|
||||
|
||||
if (processor != nullptr)
|
||||
{
|
||||
if (isPrepared)
|
||||
processor->releaseResources();
|
||||
|
||||
auto* 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);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
//==============================================================================
|
||||
#if JUCE_UNIT_TESTS
|
||||
|
||||
struct AudioProcessorPlayerTests : public UnitTest
|
||||
{
|
||||
AudioProcessorPlayerTests()
|
||||
: UnitTest ("AudioProcessorPlayer", UnitTestCategories::audio) {}
|
||||
|
||||
void runTest() override
|
||||
{
|
||||
struct Layout
|
||||
{
|
||||
int numIns, numOuts;
|
||||
};
|
||||
|
||||
const Layout processorLayouts[] { Layout { 0, 0 },
|
||||
Layout { 1, 1 },
|
||||
Layout { 4, 4 },
|
||||
Layout { 4, 8 },
|
||||
Layout { 8, 4 } };
|
||||
|
||||
beginTest ("Buffers are prepared correctly for a variety of channel layouts");
|
||||
{
|
||||
for (const auto& layout : processorLayouts)
|
||||
{
|
||||
for (const auto numSystemInputs : { 0, 1, layout.numIns })
|
||||
{
|
||||
const int numSamples = 256;
|
||||
const auto systemIns = getTestBuffer (numSystemInputs, numSamples);
|
||||
auto systemOuts = getTestBuffer (layout.numOuts, numSamples);
|
||||
AudioBuffer<float> tempBuffer (jmax (layout.numIns, layout.numOuts), numSamples);
|
||||
std::vector<float*> channels ((size_t) jmax (layout.numIns, layout.numOuts), nullptr);
|
||||
|
||||
initialiseIoBuffers ({ systemIns.getArrayOfReadPointers(), systemIns.getNumChannels() },
|
||||
{ systemOuts.getArrayOfWritePointers(), systemOuts.getNumChannels() },
|
||||
numSamples,
|
||||
layout.numIns,
|
||||
layout.numOuts,
|
||||
tempBuffer,
|
||||
channels);
|
||||
|
||||
int channelIndex = 0;
|
||||
|
||||
for (const auto& channel : channels)
|
||||
{
|
||||
const auto value = [&]
|
||||
{
|
||||
// Any channels past the number of inputs should be silent.
|
||||
if (layout.numIns <= channelIndex)
|
||||
return 0.0f;
|
||||
|
||||
// If there's no input, all input channels should be silent.
|
||||
if (numSystemInputs == 0) return 0.0f;
|
||||
|
||||
// If there's one input, all input channels should copy from that input.
|
||||
if (numSystemInputs == 1) return 1.0f;
|
||||
|
||||
// Otherwise, each processor input should match the corresponding system input.
|
||||
return (float) (channelIndex + 1);
|
||||
}();
|
||||
|
||||
expect (FloatVectorOperations::findMinAndMax (channel, numSamples) == Range<float> (value, value));
|
||||
|
||||
channelIndex += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static AudioBuffer<float> getTestBuffer (int numChannels, int numSamples)
|
||||
{
|
||||
AudioBuffer<float> result (numChannels, numSamples);
|
||||
|
||||
for (int i = 0; i < result.getNumChannels(); ++i)
|
||||
FloatVectorOperations::fill (result.getWritePointer (i), (float) i + 1, result.getNumSamples());
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
static AudioProcessorPlayerTests audioProcessorPlayerTests;
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
144
deps/juce/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.h
vendored
Normal file
144
deps/juce/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.h
vendored
Normal file
@ -0,0 +1,144 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
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. To set a MidiOutput for the processor,
|
||||
use the setMidiOutput() method.
|
||||
|
||||
@see AudioProcessor, AudioProcessorGraph
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class JUCE_API AudioProcessorPlayer : public AudioIODeviceCallback,
|
||||
public MidiInputCallback
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
AudioProcessorPlayer (bool doDoublePrecisionProcessing = false);
|
||||
|
||||
/** Destructor. */
|
||||
~AudioProcessorPlayer() override;
|
||||
|
||||
//==============================================================================
|
||||
/** 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; }
|
||||
|
||||
/** Sets the MIDI output that should be used, if required.
|
||||
|
||||
The MIDI output will not be deleted or owned by this object. If the MIDI output is
|
||||
deleted, pass a nullptr to this method.
|
||||
*/
|
||||
void setMidiOutput (MidiOutput* midiOutputToUse);
|
||||
|
||||
/** Switch between double and single floating point precisions processing.
|
||||
|
||||
The audio IO callbacks will still operate in single floating point precision,
|
||||
however, all internal processing including the AudioProcessor will be processed in
|
||||
double floating point precision if the AudioProcessor supports it (see
|
||||
AudioProcessor::supportsDoublePrecisionProcessing()). Otherwise, the processing will
|
||||
remain single precision irrespective of the parameter doublePrecision.
|
||||
*/
|
||||
void setDoublePrecisionProcessing (bool doublePrecision);
|
||||
|
||||
/** Returns true if this player processes internally processes the samples with
|
||||
double floating point precision.
|
||||
*/
|
||||
inline bool getDoublePrecisionProcessing() { return isDoublePrecision; }
|
||||
|
||||
//==============================================================================
|
||||
/** @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:
|
||||
struct NumChannels
|
||||
{
|
||||
NumChannels() = default;
|
||||
NumChannels (int numIns, int numOuts) : ins (numIns), outs (numOuts) {}
|
||||
|
||||
explicit NumChannels (const AudioProcessor::BusesLayout& layout)
|
||||
: ins (layout.getNumChannels (true, 0)), outs (layout.getNumChannels (false, 0)) {}
|
||||
|
||||
AudioProcessor::BusesLayout toLayout() const
|
||||
{
|
||||
return { { AudioChannelSet::canonicalChannelSet (ins) },
|
||||
{ AudioChannelSet::canonicalChannelSet (outs) } };
|
||||
}
|
||||
|
||||
int ins = 0, outs = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
NumChannels findMostSuitableLayout (const AudioProcessor&) const;
|
||||
void resizeChannels();
|
||||
|
||||
//==============================================================================
|
||||
AudioProcessor* processor = nullptr;
|
||||
CriticalSection lock;
|
||||
double sampleRate = 0;
|
||||
int blockSize = 0;
|
||||
bool isPrepared = false, isDoublePrecision = false;
|
||||
|
||||
NumChannels deviceChannels, defaultProcessorChannels, actualProcessorChannels;
|
||||
std::vector<float*> channels;
|
||||
AudioBuffer<float> tempBuffer;
|
||||
AudioBuffer<double> conversionBuffer;
|
||||
|
||||
MidiBuffer incomingMidi;
|
||||
MidiMessageCollector messageCollector;
|
||||
MidiOutput* midiOutput = nullptr;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioProcessorPlayer)
|
||||
};
|
||||
|
||||
} // namespace juce
|
306
deps/juce/modules/juce_audio_utils/players/juce_SoundPlayer.cpp
vendored
Normal file
306
deps/juce/modules/juce_audio_utils/players/juce_SoundPlayer.cpp
vendored
Normal file
@ -0,0 +1,306 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
// This is an AudioTransportSource which will own it's assigned source
|
||||
struct AudioSourceOwningTransportSource : public AudioTransportSource
|
||||
{
|
||||
AudioSourceOwningTransportSource (PositionableAudioSource* s, double sr) : source (s)
|
||||
{
|
||||
AudioTransportSource::setSource (s, 0, nullptr, sr);
|
||||
}
|
||||
|
||||
~AudioSourceOwningTransportSource()
|
||||
{
|
||||
setSource (nullptr);
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<PositionableAudioSource> source;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioSourceOwningTransportSource)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
// An AudioSourcePlayer which will remove itself from the AudioDeviceManager's
|
||||
// callback list once it finishes playing its source
|
||||
struct AutoRemovingTransportSource : public AudioTransportSource,
|
||||
private Timer
|
||||
{
|
||||
AutoRemovingTransportSource (MixerAudioSource& mixerToUse, AudioTransportSource* ts, bool ownSource,
|
||||
int samplesPerBlock, double requiredSampleRate)
|
||||
: mixer (mixerToUse), transportSource (ts, ownSource)
|
||||
{
|
||||
jassert (ts != nullptr);
|
||||
|
||||
setSource (transportSource);
|
||||
|
||||
prepareToPlay (samplesPerBlock, requiredSampleRate);
|
||||
start();
|
||||
|
||||
mixer.addInputSource (this, true);
|
||||
startTimerHz (10);
|
||||
}
|
||||
|
||||
~AutoRemovingTransportSource() override
|
||||
{
|
||||
setSource (nullptr);
|
||||
}
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
if (! transportSource->isPlaying())
|
||||
mixer.removeInputSource (this);
|
||||
}
|
||||
|
||||
private:
|
||||
MixerAudioSource& mixer;
|
||||
OptionalScopedPointer<AudioTransportSource> transportSource;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AutoRemovingTransportSource)
|
||||
};
|
||||
|
||||
// An AudioSource which simply outputs a buffer
|
||||
class AudioBufferSource : public PositionableAudioSource
|
||||
{
|
||||
public:
|
||||
AudioBufferSource (AudioBuffer<float>* audioBuffer, bool ownBuffer, bool playOnAllChannels)
|
||||
: buffer (audioBuffer, ownBuffer),
|
||||
playAcrossAllChannels (playOnAllChannels),
|
||||
loopLen(buffer->getNumSamples())
|
||||
{}
|
||||
|
||||
//==============================================================================
|
||||
void setNextReadPosition (int64 newPosition) override
|
||||
{
|
||||
jassert (newPosition >= 0);
|
||||
|
||||
if (looping)
|
||||
newPosition = newPosition % static_cast<int64> (buffer->getNumSamples());
|
||||
|
||||
position = jmin (buffer->getNumSamples(), static_cast<int> (newPosition));
|
||||
}
|
||||
|
||||
int64 getNextReadPosition() const override { return static_cast<int64> (position); }
|
||||
int64 getTotalLength() const override { return static_cast<int64> (buffer->getNumSamples()); }
|
||||
|
||||
bool isLooping() const override { return looping; }
|
||||
void setLooping (bool shouldLoop) override { looping = shouldLoop; }
|
||||
|
||||
void setLoopRange (int64 loopStart, int64 loopLength) override {
|
||||
loopStartPos = jmax(0, jmin(static_cast<int>(loopStart), static_cast<int>(buffer->getNumSamples()) - 1));
|
||||
loopLen = jmax(1, jmin(static_cast<int>(buffer->getNumSamples()) - loopStartPos, static_cast<int>(loopLength)));
|
||||
}
|
||||
void getLoopRange(int64 & loopStart, int64 & loopLength) const override {
|
||||
loopStart = loopStartPos; loopLength = loopLen;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void prepareToPlay (int, double) override {}
|
||||
void releaseResources() override {}
|
||||
|
||||
void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override
|
||||
{
|
||||
bufferToFill.clearActiveBufferRegion();
|
||||
|
||||
const int bufferSize = buffer->getNumSamples();
|
||||
int samplesNeeded = bufferToFill.numSamples;
|
||||
|
||||
while (samplesNeeded > 0) {
|
||||
|
||||
const int samplesToCopy = jmin (looping ? (loopStartPos + loopLen) - position : bufferSize - position, samplesNeeded);
|
||||
|
||||
if (samplesToCopy > 0)
|
||||
{
|
||||
int maxInChannels = buffer->getNumChannels();
|
||||
int maxOutChannels = bufferToFill.buffer->getNumChannels();
|
||||
|
||||
if (! playAcrossAllChannels) {
|
||||
maxOutChannels = jmin (maxOutChannels, maxInChannels);
|
||||
}
|
||||
|
||||
for (int i = 0; i < maxOutChannels; ++i) {
|
||||
bufferToFill.buffer->copyFrom (i, bufferToFill.startSample, *buffer,
|
||||
i % maxInChannels, position, samplesToCopy);
|
||||
}
|
||||
|
||||
position += samplesToCopy;
|
||||
samplesNeeded -= samplesToCopy;
|
||||
}
|
||||
else {
|
||||
position += samplesNeeded;
|
||||
samplesNeeded = 0;
|
||||
}
|
||||
|
||||
if (looping) {
|
||||
int posdelta = position - (loopStartPos + loopLen);
|
||||
if (posdelta >= 0) {
|
||||
position = loopStartPos + posdelta;
|
||||
}
|
||||
}
|
||||
else {
|
||||
position += samplesNeeded - samplesToCopy;
|
||||
samplesNeeded = 0; // force to be done
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
OptionalScopedPointer<AudioBuffer<float>> buffer;
|
||||
int position = 0;
|
||||
bool looping = false, playAcrossAllChannels;
|
||||
int loopStartPos = 0, loopLen;
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioBufferSource)
|
||||
};
|
||||
|
||||
SoundPlayer::SoundPlayer()
|
||||
: sampleRate (44100.0), bufferSize (512)
|
||||
{
|
||||
formatManager.registerBasicFormats();
|
||||
player.setSource (&mixer);
|
||||
}
|
||||
|
||||
SoundPlayer::~SoundPlayer()
|
||||
{
|
||||
mixer.removeAllInputs();
|
||||
player.setSource (nullptr);
|
||||
}
|
||||
|
||||
void SoundPlayer::play (const File& file)
|
||||
{
|
||||
if (file.existsAsFile())
|
||||
play (formatManager.createReaderFor (file), true);
|
||||
}
|
||||
|
||||
void SoundPlayer::play (const void* resourceData, size_t resourceSize)
|
||||
{
|
||||
if (resourceData != nullptr && resourceSize > 0)
|
||||
{
|
||||
auto mem = std::make_unique<MemoryInputStream> (resourceData, resourceSize, false);
|
||||
play (formatManager.createReaderFor (std::move (mem)), true);
|
||||
}
|
||||
}
|
||||
|
||||
void SoundPlayer::play (AudioFormatReader* reader, bool deleteWhenFinished)
|
||||
{
|
||||
if (reader != nullptr)
|
||||
play (new AudioFormatReaderSource (reader, deleteWhenFinished), true, reader->sampleRate);
|
||||
}
|
||||
|
||||
void SoundPlayer::play (AudioBuffer<float>* buffer, bool deleteWhenFinished, bool playOnAllOutputChannels)
|
||||
{
|
||||
if (buffer != nullptr)
|
||||
play (new AudioBufferSource (buffer, deleteWhenFinished, playOnAllOutputChannels), true);
|
||||
}
|
||||
|
||||
void SoundPlayer::play (PositionableAudioSource* audioSource, bool deleteWhenFinished, double fileSampleRate)
|
||||
{
|
||||
if (audioSource != nullptr)
|
||||
{
|
||||
AudioTransportSource* transport = dynamic_cast<AudioTransportSource*> (audioSource);
|
||||
|
||||
if (transport == nullptr)
|
||||
{
|
||||
if (deleteWhenFinished)
|
||||
{
|
||||
transport = new AudioSourceOwningTransportSource (audioSource, fileSampleRate);
|
||||
}
|
||||
else
|
||||
{
|
||||
transport = new AudioTransportSource();
|
||||
transport->setSource (audioSource, 0, nullptr, fileSampleRate);
|
||||
deleteWhenFinished = true;
|
||||
}
|
||||
}
|
||||
|
||||
transport->start();
|
||||
transport->prepareToPlay (bufferSize, sampleRate);
|
||||
|
||||
new AutoRemovingTransportSource (mixer, transport, deleteWhenFinished, bufferSize, sampleRate);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (deleteWhenFinished)
|
||||
delete audioSource;
|
||||
}
|
||||
}
|
||||
|
||||
void SoundPlayer::playTestSound()
|
||||
{
|
||||
auto soundLength = (int) sampleRate;
|
||||
double frequency = 440.0;
|
||||
float amplitude = 0.5f;
|
||||
|
||||
auto phasePerSample = MathConstants<double>::twoPi / (sampleRate / frequency);
|
||||
|
||||
auto* newSound = new AudioBuffer<float> (1, soundLength);
|
||||
|
||||
for (int i = 0; i < soundLength; ++i)
|
||||
newSound->setSample (0, i, amplitude * (float) std::sin (i * phasePerSample));
|
||||
|
||||
newSound->applyGainRamp (0, 0, soundLength / 10, 0.0f, 1.0f);
|
||||
newSound->applyGainRamp (0, soundLength - soundLength / 4, soundLength / 4, 1.0f, 0.0f);
|
||||
|
||||
play (newSound, true, true);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void SoundPlayer::audioDeviceIOCallback (const float** inputChannelData,
|
||||
int numInputChannels,
|
||||
float** outputChannelData,
|
||||
int numOutputChannels,
|
||||
int numSamples)
|
||||
{
|
||||
player.audioDeviceIOCallback (inputChannelData, numInputChannels,
|
||||
outputChannelData, numOutputChannels,
|
||||
numSamples);
|
||||
}
|
||||
|
||||
void SoundPlayer::audioDeviceAboutToStart (AudioIODevice* device)
|
||||
{
|
||||
if (device != nullptr)
|
||||
{
|
||||
sampleRate = device->getCurrentSampleRate();
|
||||
bufferSize = device->getCurrentBufferSizeSamples();
|
||||
}
|
||||
|
||||
player.audioDeviceAboutToStart (device);
|
||||
}
|
||||
|
||||
void SoundPlayer::audioDeviceStopped()
|
||||
{
|
||||
player.audioDeviceStopped();
|
||||
}
|
||||
|
||||
void SoundPlayer::audioDeviceError (const String& errorMessage)
|
||||
{
|
||||
player.audioDeviceError (errorMessage);
|
||||
}
|
||||
|
||||
} // namespace juce
|
136
deps/juce/modules/juce_audio_utils/players/juce_SoundPlayer.h
vendored
Normal file
136
deps/juce/modules/juce_audio_utils/players/juce_SoundPlayer.h
vendored
Normal file
@ -0,0 +1,136 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A simple sound player that you can add to the AudioDeviceManager to play
|
||||
simple sounds.
|
||||
|
||||
@see AudioProcessor, AudioProcessorGraph
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class JUCE_API SoundPlayer : public AudioIODeviceCallback
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
SoundPlayer();
|
||||
|
||||
/** Destructor. */
|
||||
~SoundPlayer() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Plays a sound from a file. */
|
||||
void play (const File& file);
|
||||
|
||||
/** Convenient method to play sound from a JUCE resource. */
|
||||
void play (const void* resourceData, size_t resourceSize);
|
||||
|
||||
/** Plays the sound from an audio format reader.
|
||||
|
||||
If deleteWhenFinished is true then the format reader will be
|
||||
automatically deleted once the sound has finished playing.
|
||||
*/
|
||||
void play (AudioFormatReader* buffer, bool deleteWhenFinished = false);
|
||||
|
||||
/** Plays the sound from a positionable audio source.
|
||||
|
||||
This will output the sound coming from a positionable audio source.
|
||||
This gives you slightly more control over the sound playback compared
|
||||
to the other playSound methods. For example, if you would like to
|
||||
stop the sound prematurely you can call this method with a
|
||||
TransportAudioSource and then call audioSource->stop. Note that,
|
||||
you must call audioSource->start to start the playback, if your
|
||||
audioSource is a TransportAudioSource.
|
||||
|
||||
The audio device manager will not hold any references to this audio
|
||||
source once the audio source has stopped playing for any reason,
|
||||
for example when the sound has finished playing or when you have
|
||||
called audioSource->stop. Therefore, calling audioSource->start() on
|
||||
a finished audioSource will not restart the sound again. If this is
|
||||
desired simply call playSound with the same audioSource again.
|
||||
|
||||
@param audioSource the audio source to play
|
||||
@param deleteWhenFinished If this is true then the audio source will
|
||||
be deleted once the device manager has finished
|
||||
playing.
|
||||
@param sampleRateOfSource The sample rate of the source. If this is zero, JUCE
|
||||
will assume that the sample rate is the same as the
|
||||
audio output device.
|
||||
*/
|
||||
void play (PositionableAudioSource* audioSource, bool deleteWhenFinished = false,
|
||||
double sampleRateOfSource = 0.0);
|
||||
|
||||
/** Plays the sound from an audio sample buffer.
|
||||
|
||||
This will output the sound contained in an audio sample buffer. If
|
||||
deleteWhenFinished is true then the audio sample buffer will be
|
||||
automatically deleted once the sound has finished playing.
|
||||
|
||||
If playOnAllOutputChannels is true, then if there are more output channels
|
||||
than buffer channels, then the ones that are available will be re-used on
|
||||
multiple outputs so that something is sent to all output channels. If it
|
||||
is false, then the buffer will just be played on the first output channels.
|
||||
*/
|
||||
void play (AudioBuffer<float>* buffer,
|
||||
bool deleteWhenFinished = false,
|
||||
bool playOnAllOutputChannels = false);
|
||||
|
||||
/** Plays a beep through the current audio device.
|
||||
|
||||
This is here to allow the audio setup UI panels to easily include a "test"
|
||||
button so that the user can check where the audio is coming from.
|
||||
*/
|
||||
void playTestSound();
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void audioDeviceIOCallback (const float**, int, float**, int, int) override;
|
||||
/** @internal */
|
||||
void audioDeviceAboutToStart (AudioIODevice*) override;
|
||||
/** @internal */
|
||||
void audioDeviceStopped() override;
|
||||
/** @internal */
|
||||
void audioDeviceError (const String& errorMessage) override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
AudioFormatManager formatManager;
|
||||
AudioSourcePlayer player;
|
||||
MixerAudioSource mixer;
|
||||
OwnedArray<AudioSource> sources;
|
||||
|
||||
//==============================================================================
|
||||
double sampleRate;
|
||||
int bufferSize;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SoundPlayer)
|
||||
};
|
||||
|
||||
} // namespace juce
|
Reference in New Issue
Block a user