migrating to the latest JUCE version

This commit is contained in:
2022-11-04 23:11:33 +01:00
committed by Nikolai Rodionov
parent 4257a0f8ba
commit faf8f18333
2796 changed files with 888518 additions and 784244 deletions

View File

@ -1,172 +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
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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

View File

@ -1,63 +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
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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

View File

@ -1,177 +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
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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

View File

@ -1,89 +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
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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

View File

@ -1,135 +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
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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

File diff suppressed because it is too large Load Diff

View File

@ -1,121 +1,119 @@
/*
==============================================================================
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
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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
{
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 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

File diff suppressed because it is too large Load Diff

View File

@ -1,222 +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
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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;
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

View File

@ -1,158 +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
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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

View File

@ -1,197 +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
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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

View File

@ -1,118 +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
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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 cacheable 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

View File

@ -1,222 +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
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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

View File

@ -1,133 +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
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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

View File

@ -1,83 +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
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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 targeting 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

View File

@ -0,0 +1,462 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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
{
constexpr uint8 whiteNotes[] = { 0, 2, 4, 5, 7, 9, 11 };
constexpr uint8 blackNotes[] = { 1, 3, 6, 8, 10 };
//==============================================================================
struct KeyboardComponentBase::UpDownButton : public Button
{
UpDownButton (KeyboardComponentBase& c, int d)
: Button ({}), owner (c), delta (d)
{
}
void clicked() override
{
auto note = owner.getLowestVisibleKey();
note = delta < 0 ? (note - 1) / 12 : 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:
KeyboardComponentBase& owner;
int delta;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UpDownButton)
};
//==============================================================================
KeyboardComponentBase::KeyboardComponentBase (Orientation o) : orientation (o)
{
scrollDown = std::make_unique<UpDownButton> (*this, -1);
scrollUp = std::make_unique<UpDownButton> (*this, 1);
addChildComponent (*scrollDown);
addChildComponent (*scrollUp);
colourChanged();
}
//==============================================================================
void KeyboardComponentBase::setKeyWidth (float widthInPixels)
{
jassert (widthInPixels > 0);
if (keyWidth != widthInPixels) // Prevent infinite recursion if the width is being computed in a 'resized()' callback
{
keyWidth = widthInPixels;
resized();
}
}
void KeyboardComponentBase::setScrollButtonWidth (int widthInPixels)
{
jassert (widthInPixels > 0);
if (scrollButtonWidth != widthInPixels)
{
scrollButtonWidth = widthInPixels;
resized();
}
}
void KeyboardComponentBase::setOrientation (Orientation newOrientation)
{
if (orientation != newOrientation)
{
orientation = newOrientation;
resized();
}
}
void KeyboardComponentBase::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 KeyboardComponentBase::setLowestVisibleKey (int noteNumber)
{
setLowestVisibleKeyFloat ((float) noteNumber);
}
void KeyboardComponentBase::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();
}
}
float KeyboardComponentBase::getWhiteNoteLength() const noexcept
{
return (orientation == horizontalKeyboard) ? (float) getHeight() : (float) getWidth();
}
void KeyboardComponentBase::setBlackNoteLengthProportion (float ratio) noexcept
{
jassert (ratio >= 0.0f && ratio <= 1.0f);
if (blackNoteLengthRatio != ratio)
{
blackNoteLengthRatio = ratio;
resized();
}
}
float KeyboardComponentBase::getBlackNoteLength() const noexcept
{
auto whiteNoteLength = orientation == horizontalKeyboard ? getHeight() : getWidth();
return (float) whiteNoteLength * blackNoteLengthRatio;
}
void KeyboardComponentBase::setBlackNoteWidthProportion (float ratio) noexcept
{
jassert (ratio >= 0.0f && ratio <= 1.0f);
if (blackNoteWidthRatio != ratio)
{
blackNoteWidthRatio = ratio;
resized();
}
}
void KeyboardComponentBase::setScrollButtonsVisible (bool newCanScroll)
{
if (canScroll != newCanScroll)
{
canScroll = newCanScroll;
resized();
}
}
//==============================================================================
Range<float> KeyboardComponentBase::getKeyPos (int midiNoteNumber) const
{
return getKeyPosition (midiNoteNumber, keyWidth)
- xOffset
- getKeyPosition (rangeStart, keyWidth).getStart();
}
float KeyboardComponentBase::getKeyStartPosition (int midiNoteNumber) const
{
return getKeyPos (midiNoteNumber).getStart();
}
float KeyboardComponentBase::getTotalKeyboardWidth() const noexcept
{
return getKeyPos (rangeEnd).getEnd();
}
KeyboardComponentBase::NoteAndVelocity KeyboardComponentBase::getNoteAndVelocityAtPosition (Point<float> pos, bool children)
{
if (! reallyContains (pos, children))
return { -1, 0.0f };
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));
}
KeyboardComponentBase::NoteAndVelocity KeyboardComponentBase::remappedXYToNote (Point<float> pos) 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 (rangeStart <= note && note <= rangeEnd)
{
if (getKeyPos (note).contains (pos.x - xOffset))
{
return { note, jmax (0.0f, pos.y / blackNoteLength) };
}
}
}
}
}
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();
return { note, jmax (0.0f, pos.y / (float) whiteNoteLength) };
}
}
}
}
return { -1, 0 };
}
Rectangle<float> KeyboardComponentBase::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 {};
}
//==============================================================================
void KeyboardComponentBase::setOctaveForMiddleC (int octaveNum)
{
octaveNumForMiddleC = octaveNum;
repaint();
}
//==============================================================================
void KeyboardComponentBase::drawUpDownButton (Graphics& g, int w, int h, bool mouseOver, bool buttonDown, bool movesOctavesUp)
{
g.fillAll (findColour (upDownButtonBackgroundColourId));
float angle = 0;
switch (getOrientation())
{
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));
}
Range<float> KeyboardComponentBase::getKeyPosition (int midiNoteNumber, float targetKeyWidth) const
{
auto ratio = getBlackNoteWidthProportion();
static const float notePos[] = { 0.0f, 1 - ratio * 0.6f,
1.0f, 2 - ratio * 0.4f,
2.0f,
3.0f, 4 - ratio * 0.7f,
4.0f, 5 - ratio * 0.5f,
5.0f, 6 - ratio * 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 };
}
//==============================================================================
void KeyboardComponentBase::paint (Graphics& g)
{
drawKeyboardBackground (g, getLocalBounds().toFloat());
for (int octaveBase = 0; octaveBase < 128; octaveBase += 12)
{
for (auto noteNum : whiteNotes)
{
const auto key = octaveBase + noteNum;
if (rangeStart <= key && key <= rangeEnd)
drawWhiteKey (key, g, getRectangleForKey (key));
}
for (auto noteNum : blackNotes)
{
const auto key = octaveBase + noteNum;
if (rangeStart <= key && key <= rangeEnd)
drawBlackKey (key, g, getRectangleForKey (key));
}
}
}
void KeyboardComponentBase::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();
auto spaceAvailable = w;
auto lastStartKey = remappedXYToNote ({ endOfLastKey - (float) spaceAvailable, 0 }).note + 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 KeyboardComponentBase::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);
}
} // namespace juce

View File

@ -0,0 +1,295 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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 drawing a custom MIDI keyboard component.
Implement the drawKeyboardBackground(), drawWhiteKey(), and drawBlackKey() methods
to draw your content and this class will handle the underlying keyboard logic.
The component is a ChangeBroadcaster, so if you want to be informed when the
keyboard is scrolled, you can register a ChangeListener for callbacks.
@tags{Audio}
*/
class JUCE_API KeyboardComponentBase : public Component,
public ChangeBroadcaster
{
public:
//==============================================================================
/** The direction of the keyboard.
@see setOrientation
*/
enum Orientation
{
horizontalKeyboard,
verticalKeyboardFacingLeft,
verticalKeyboardFacingRight,
};
//==============================================================================
/** Constructor.
@param orientation whether the keyboard is horizontal or vertical
*/
explicit KeyboardComponentBase (Orientation orientation);
/** Destructor. */
~KeyboardComponentBase() override = default;
//==============================================================================
/** 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; }
/** Returns true if the keyboard's orientation is horizontal. */
bool isHorizontal() const noexcept { return orientation == horizontalKeyboard; }
/** Sets the range of midi notes that the keyboard will be limited to.
By default the range is 0 to 127 (inclusive), but you can limit this if you
only want a restricted set of the keys to be shown.
Note that the values here are inclusive and must be between 0 and 127.
*/
void setAvailableRange (int lowestNote, int highestNote);
/** Returns the first note in the available range.
@see setAvailableRange
*/
int getRangeStart() const noexcept { return rangeStart; }
/** Returns the last note in the available range.
@see setAvailableRange
*/
int getRangeEnd() const noexcept { return rangeEnd; }
/** If the keyboard extends beyond the size of the component, this will scroll
it to show the given key at the start.
Whenever the keyboard's position is changed, this will use the ChangeBroadcaster
base class to send a callback to any ChangeListeners that have been registered.
*/
void setLowestVisibleKey (int noteNumber);
/** Returns the number of the first key shown in the component.
@see setLowestVisibleKey
*/
int getLowestVisibleKey() const noexcept { return (int) firstKey; }
/** Returns the absolute length of the white notes.
This will be their vertical or horizontal length, depending on the keyboard's orientation.
*/
float getWhiteNoteLength() const noexcept;
/** 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);
//==============================================================================
/** Colour IDs to use to change the colour of the octave scroll buttons.
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
{
upDownButtonBackgroundColourId = 0x1004000,
upDownButtonArrowColourId = 0x1004001
};
/** 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;
/** This structure is returned by the getNoteAndVelocityAtPosition() method.
*/
struct JUCE_API NoteAndVelocity
{
int note;
float velocity;
};
/** Returns the note number and velocity for a given position within the component.
If includeChildComponents is true then this will return a key obscured by any child
components.
*/
NoteAndVelocity getNoteAndVelocityAtPosition (Point<float> position, bool includeChildComponents = false);
#ifndef DOXYGEN
/** Returns the key at a given coordinate, or -1 if the position does not intersect a key. */
[[deprecated ("This method has been deprecated in favour of getNoteAndVelocityAtPosition.")]]
int getNoteAtPosition (Point<float> p) { return getNoteAndVelocityAtPosition (p).note; }
#endif
/** Returns the rectangle for a given key. */
Rectangle<float> getRectangleForKey (int midiNoteNumber) const;
//==============================================================================
/** 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; }
//==============================================================================
/** Use this method to draw the background of the keyboard that will be drawn under
the white and black notes. This can also be used to draw any shadow or outline effects.
*/
virtual void drawKeyboardBackground (Graphics& g, Rectangle<float> area) = 0;
/** Use this method to draw a white key of the keyboard in a given rectangle.
When doing this, be sure to note the keyboard's orientation.
*/
virtual void drawWhiteKey (int midiNoteNumber, Graphics& g, Rectangle<float> area) = 0;
/** Use this method to draw a black key of the keyboard in a given rectangle.
When doing this, be sure to note the keyboard's orientation.
*/
virtual void drawBlackKey (int midiNoteNumber, Graphics& g, Rectangle<float> area) = 0;
/** This can be overridden to draw 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);
/** 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;
//==============================================================================
/** @internal */
void paint (Graphics&) override;
/** @internal */
void resized() override;
/** @internal */
void mouseWheelMove (const MouseEvent&, const MouseWheelDetails&) override;
private:
//==============================================================================
struct UpDownButton;
Range<float> getKeyPos (int midiNoteNumber) const;
NoteAndVelocity remappedXYToNote (Point<float>) const;
void setLowestVisibleKeyFloat (float noteNumber);
//==============================================================================
Orientation orientation;
float blackNoteLengthRatio = 0.7f, blackNoteWidthRatio = 0.7f;
float xOffset = 0.0f;
float keyWidth = 16.0f;
float firstKey = 12 * 4.0f;
int scrollButtonWidth = 12;
int rangeStart = 0, rangeEnd = 127;
int octaveNumForMiddleC = 3;
bool canScroll = true;
std::unique_ptr<Button> scrollDown, scrollUp;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (KeyboardComponentBase)
};
} // namespace juce

View File

@ -0,0 +1,507 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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 MPEKeyboardComponent::MPENoteComponent : public Component
{
MPENoteComponent (MPEKeyboardComponent& o, uint16 sID, uint8 initial, float noteOnVel, float press)
: owner (o),
radiusScale (owner.getKeyWidth() / 1.5f),
noteOnVelocity (noteOnVel),
pressure (press),
sourceID (sID),
initialNote (initial)
{
}
float getStrikeRadius() const { return 5.0f + getNoteOnVelocity() * radiusScale * 2.0f; }
float getPressureRadius() const { return 5.0f + getPressure() * radiusScale * 2.0f; }
float getNoteOnVelocity() const { return noteOnVelocity; }
float getPressure() const { return pressure; }
Point<float> getCentrePos() const { return getBounds().toFloat().getCentre(); }
void paint (Graphics& g) override
{
auto strikeSize = getStrikeRadius() * 2.0f;
auto pressSize = getPressureRadius() * 2.0f;
auto bounds = getLocalBounds().toFloat();
g.setColour (owner.findColour (noteCircleFillColourId));
g.fillEllipse (bounds.withSizeKeepingCentre (strikeSize, strikeSize));
g.setColour (owner.findColour (noteCircleOutlineColourId));
g.drawEllipse (bounds.withSizeKeepingCentre (pressSize, pressSize), 1.0f);
}
//==========================================================================
MPEKeyboardComponent& owner;
float radiusScale = 0.0f, noteOnVelocity = 0.0f, pressure = 0.5f;
uint16 sourceID = 0;
uint8 initialNote = 0;
bool isLatched = true;
};
//==============================================================================
MPEKeyboardComponent::MPEKeyboardComponent (MPEInstrument& instr, Orientation orientationToUse)
: KeyboardComponentBase (orientationToUse),
instrument (instr)
{
updateZoneLayout();
colourChanged();
setKeyWidth (25.0f);
instrument.addListener (this);
}
MPEKeyboardComponent::~MPEKeyboardComponent()
{
instrument.removeListener (this);
}
//==============================================================================
void MPEKeyboardComponent::drawKeyboardBackground (Graphics& g, Rectangle<float> area)
{
g.setColour (findColour (whiteNoteColourId));
g.fillRect (area);
}
void MPEKeyboardComponent::drawWhiteKey (int midiNoteNumber, Graphics& g, Rectangle<float> area)
{
if (midiNoteNumber % 12 == 0)
{
auto fontHeight = jmin (12.0f, getKeyWidth() * 0.9f);
auto text = MidiMessage::getMidiNoteName (midiNoteNumber, true, true, getOctaveForMiddleC());
g.setColour (findColour (textLabelColourId));
g.setFont (Font (fontHeight).withHorizontalScale (0.8f));
switch (getOrientation())
{
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;
}
}
}
void MPEKeyboardComponent::drawBlackKey (int /*midiNoteNumber*/, Graphics& g, Rectangle<float> area)
{
g.setColour (findColour (whiteNoteColourId));
g.fillRect (area);
g.setColour (findColour (blackNoteColourId));
if (isHorizontal())
{
g.fillRoundedRectangle (area.toFloat().reduced ((area.getWidth() / 2.0f) - (getBlackNoteWidth() / 12.0f),
area.getHeight() / 4.0f), 1.0f);
}
else
{
g.fillRoundedRectangle (area.toFloat().reduced (area.getWidth() / 4.0f,
(area.getHeight() / 2.0f) - (getBlackNoteWidth() / 12.0f)), 1.0f);
}
}
void MPEKeyboardComponent::colourChanged()
{
setOpaque (findColour (whiteNoteColourId).isOpaque());
repaint();
}
//==========================================================================
MPEValue MPEKeyboardComponent::mousePositionToPitchbend (int initialNote, Point<float> mousePos)
{
auto constrainedMousePos = [&]
{
auto horizontal = isHorizontal();
auto posToCheck = jlimit (0.0f,
horizontal ? (float) getWidth() - 1.0f : (float) getHeight(),
horizontal ? mousePos.x : mousePos.y);
auto bottomKeyRange = getRectangleForKey (jmax (getRangeStart(), initialNote - perNotePitchbendRange));
auto topKeyRange = getRectangleForKey (jmin (getRangeEnd(), initialNote + perNotePitchbendRange));
auto lowerLimit = horizontal ? bottomKeyRange.getCentreX()
: getOrientation() == Orientation::verticalKeyboardFacingRight ? topKeyRange.getCentreY()
: bottomKeyRange.getCentreY();
auto upperLimit = horizontal ? topKeyRange.getCentreX()
: getOrientation() == Orientation::verticalKeyboardFacingRight ? bottomKeyRange.getCentreY()
: topKeyRange.getCentreY();
posToCheck = jlimit (lowerLimit, upperLimit, posToCheck);
return horizontal ? Point<float> (posToCheck, 0.0f)
: Point<float> (0.0f, posToCheck);
}();
auto note = getNoteAndVelocityAtPosition (constrainedMousePos, true).note;
if (note == -1)
{
jassertfalse;
return {};
}
auto fractionalSemitoneBend = [&]
{
auto noteRect = getRectangleForKey (note);
switch (getOrientation())
{
case horizontalKeyboard: return (constrainedMousePos.x - noteRect.getCentreX()) / noteRect.getWidth();
case verticalKeyboardFacingRight: return (noteRect.getCentreY() - constrainedMousePos.y) / noteRect.getHeight();
case verticalKeyboardFacingLeft: return (constrainedMousePos.y - noteRect.getCentreY()) / noteRect.getHeight();
}
jassertfalse;
return 0.0f;
}();
auto totalNumSemitones = ((float) note + fractionalSemitoneBend) - (float) initialNote;
return MPEValue::fromUnsignedFloat (jmap (totalNumSemitones, (float) -perNotePitchbendRange, (float) perNotePitchbendRange, 0.0f, 1.0f));
}
MPEValue MPEKeyboardComponent::mousePositionToTimbre (Point<float> mousePos)
{
auto delta = [mousePos, this]
{
switch (getOrientation())
{
case horizontalKeyboard: return mousePos.y;
case verticalKeyboardFacingLeft: return (float) getWidth() - mousePos.x;
case verticalKeyboardFacingRight: return mousePos.x;
}
jassertfalse;
return 0.0f;
}();
return MPEValue::fromUnsignedFloat (jlimit (0.0f, 1.0f, 1.0f - (delta / getWhiteNoteLength())));
}
void MPEKeyboardComponent::mouseDown (const MouseEvent& e)
{
auto newNote = getNoteAndVelocityAtPosition (e.position).note;
if (newNote >= 0)
{
auto channel = channelAssigner->findMidiChannelForNewNote (newNote);
instrument.noteOn (channel, newNote, MPEValue::fromUnsignedFloat (velocity));
sourceIDMap[e.source.getIndex()] = instrument.getNote (instrument.getNumPlayingNotes() - 1).noteID;
instrument.pitchbend (channel, MPEValue::centreValue());
instrument.timbre (channel, mousePositionToTimbre (e.position));
instrument.pressure (channel, MPEValue::fromUnsignedFloat (e.isPressureValid()
&& useMouseSourcePressureForStrike ? e.pressure
: pressure));
}
}
void MPEKeyboardComponent::mouseDrag (const MouseEvent& e)
{
auto noteID = sourceIDMap[e.source.getIndex()];
auto note = instrument.getNoteWithID (noteID);
if (! note.isValid())
return;
auto noteComponent = std::find_if (noteComponents.begin(),
noteComponents.end(),
[noteID] (auto& comp) { return comp->sourceID == noteID; });
if (noteComponent == noteComponents.end())
return;
if ((*noteComponent)->isLatched && std::abs (isHorizontal() ? e.getDistanceFromDragStartX()
: e.getDistanceFromDragStartY()) > roundToInt (getKeyWidth() / 4.0f))
{
(*noteComponent)->isLatched = false;
}
auto channel = channelAssigner->findMidiChannelForExistingNote (note.initialNote);
if (! (*noteComponent)->isLatched)
instrument.pitchbend (channel, mousePositionToPitchbend (note.initialNote, e.position));
instrument.timbre (channel, mousePositionToTimbre (e.position));
instrument.pressure (channel, MPEValue::fromUnsignedFloat (e.isPressureValid()
&& useMouseSourcePressureForStrike ? e.pressure
: pressure));
}
void MPEKeyboardComponent::mouseUp (const MouseEvent& e)
{
auto note = instrument.getNoteWithID (sourceIDMap[e.source.getIndex()]);
if (! note.isValid())
return;
instrument.noteOff (channelAssigner->findMidiChannelForExistingNote (note.initialNote),
note.initialNote, MPEValue::fromUnsignedFloat (lift));
channelAssigner->noteOff (note.initialNote);
sourceIDMap.erase (e.source.getIndex());
}
void MPEKeyboardComponent::focusLost (FocusChangeType)
{
for (auto& comp : noteComponents)
{
auto note = instrument.getNoteWithID (comp->sourceID);
if (note.isValid())
instrument.noteOff (channelAssigner->findMidiChannelForExistingNote (note.initialNote),
note.initialNote, MPEValue::fromUnsignedFloat (lift));
}
}
//==============================================================================
void MPEKeyboardComponent::updateZoneLayout()
{
{
const ScopedLock noteLock (activeNotesLock);
activeNotes.clear();
}
noteComponents.clear();
if (instrument.isLegacyModeEnabled())
{
channelAssigner = std::make_unique<MPEChannelAssigner> (instrument.getLegacyModeChannelRange());
perNotePitchbendRange = instrument.getLegacyModePitchbendRange();
}
else
{
auto layout = instrument.getZoneLayout();
if (layout.isActive())
{
auto zone = layout.getLowerZone().isActive() ? layout.getLowerZone()
: layout.getUpperZone();
channelAssigner = std::make_unique<MPEChannelAssigner> (zone);
perNotePitchbendRange = zone.perNotePitchbendRange;
}
else
{
channelAssigner.reset();
}
}
}
void MPEKeyboardComponent::addNewNote (MPENote note)
{
noteComponents.push_back (std::make_unique<MPENoteComponent> (*this, note.noteID, note.initialNote,
note.noteOnVelocity.asUnsignedFloat(),
note.pressure.asUnsignedFloat()));
auto& comp = noteComponents.back();
addAndMakeVisible (*comp);
comp->toBack();
}
void MPEKeyboardComponent::handleNoteOns (std::set<MPENote>& notesToUpdate)
{
for (auto& note : notesToUpdate)
{
if (! std::any_of (noteComponents.begin(),
noteComponents.end(),
[note] (auto& comp) { return comp->sourceID == note.noteID; }))
{
addNewNote (note);
}
}
}
void MPEKeyboardComponent::handleNoteOffs (std::set<MPENote>& notesToUpdate)
{
auto removePredicate = [&notesToUpdate] (std::unique_ptr<MPENoteComponent>& comp)
{
return std::none_of (notesToUpdate.begin(),
notesToUpdate.end(),
[&comp] (auto& note) { return comp->sourceID == note.noteID; });
};
noteComponents.erase (std::remove_if (std::begin (noteComponents),
std::end (noteComponents),
removePredicate),
std::end (noteComponents));
if (noteComponents.empty())
stopTimer();
}
void MPEKeyboardComponent::updateNoteComponentBounds (const MPENote& note, MPENoteComponent& noteComponent)
{
auto xPos = [&]
{
const auto currentNote = note.initialNote + (float) note.totalPitchbendInSemitones;
const auto noteBend = currentNote - std::floor (currentNote);
const auto noteBounds = getRectangleForKey ((int) currentNote);
const auto nextNoteBounds = getRectangleForKey ((int) currentNote + 1);
const auto horizontal = isHorizontal();
const auto distance = noteBend * (horizontal ? nextNoteBounds.getCentreX() - noteBounds.getCentreX()
: nextNoteBounds.getCentreY() - noteBounds.getCentreY());
return (horizontal ? noteBounds.getCentreX() : noteBounds.getCentreY()) + distance;
}();
auto yPos = [&]
{
const auto currentOrientation = getOrientation();
const auto timbrePosition = (currentOrientation == horizontalKeyboard
|| currentOrientation == verticalKeyboardFacingRight ? 1.0f - note.timbre.asUnsignedFloat()
: note.timbre.asUnsignedFloat());
return timbrePosition * getWhiteNoteLength();
}();
const auto centrePos = (isHorizontal() ? Point<float> (xPos, yPos)
: Point<float> (yPos, xPos));
const auto radius = jmax (noteComponent.getStrikeRadius(), noteComponent.getPressureRadius());
noteComponent.setBounds (Rectangle<float> (radius * 2.0f, radius * 2.0f)
.withCentre (centrePos)
.getSmallestIntegerContainer());
}
static bool operator< (const MPENote& n1, const MPENote& n2) noexcept { return n1.noteID < n2.noteID; }
void MPEKeyboardComponent::updateNoteComponents()
{
std::set<MPENote> notesToUpdate;
{
ScopedLock noteLock (activeNotesLock);
for (const auto& note : activeNotes)
if (note.second)
notesToUpdate.insert (note.first);
};
handleNoteOns (notesToUpdate);
handleNoteOffs (notesToUpdate);
for (auto& comp : noteComponents)
{
auto noteForComponent = std::find_if (notesToUpdate.begin(),
notesToUpdate.end(),
[&comp] (auto& note) { return note.noteID == comp->sourceID; });
if (noteForComponent != notesToUpdate.end())
{
comp->pressure = noteForComponent->pressure.asUnsignedFloat();
updateNoteComponentBounds (*noteForComponent, *comp);
comp->repaint();
}
}
}
void MPEKeyboardComponent::timerCallback()
{
updateNoteComponents();
}
//==============================================================================
void MPEKeyboardComponent::noteAdded (MPENote newNote)
{
{
const ScopedLock noteLock (activeNotesLock);
activeNotes.push_back ({ newNote, true });
}
startTimerHz (30);
}
void MPEKeyboardComponent::updateNoteData (MPENote& changedNote)
{
const ScopedLock noteLock (activeNotesLock);
for (auto& note : activeNotes)
{
if (note.first.noteID == changedNote.noteID)
{
note.first = changedNote;
note.second = true;
return;
}
}
}
void MPEKeyboardComponent::notePressureChanged (MPENote changedNote)
{
updateNoteData (changedNote);
}
void MPEKeyboardComponent::notePitchbendChanged (MPENote changedNote)
{
updateNoteData (changedNote);
}
void MPEKeyboardComponent::noteTimbreChanged (MPENote changedNote)
{
updateNoteData (changedNote);
}
void MPEKeyboardComponent::noteReleased (MPENote finishedNote)
{
const ScopedLock noteLock (activeNotesLock);
activeNotes.erase (std::remove_if (std::begin (activeNotes),
std::end (activeNotes),
[finishedNote] (auto& note) { return note.first.noteID == finishedNote.noteID; }),
std::end (activeNotes));
}
void MPEKeyboardComponent::zoneLayoutChanged()
{
MessageManager::callAsync ([this] { updateZoneLayout(); });
}
} // namespace juce

View File

@ -0,0 +1,153 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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 an MPE-compatible keyboard, whose notes can be clicked on.
This component will mimic a physical MPE-compatible keyboard, showing the current state
of an MPEInstrument object. When the on-screen keys are clicked on, it will play these
notes by calling the noteOn() and noteOff() methods of its MPEInstrument object. Moving
the mouse will update the pitchbend and timbre dimensions of the MPEInstrument.
@see MPEInstrument
@tags{Audio}
*/
class JUCE_API MPEKeyboardComponent : public KeyboardComponentBase,
private MPEInstrument::Listener,
private Timer
{
public:
//==============================================================================
/** Creates an MPEKeyboardComponent.
@param instrument the MPEInstrument that this component represents
@param orientation whether the keyboard is horizontal or vertical
*/
MPEKeyboardComponent (MPEInstrument& instrument, Orientation orientation);
/** Destructor. */
virtual ~MPEKeyboardComponent() override;
//==============================================================================
/** Sets the note-on velocity, or "strike", value that will be used when triggering new notes. */
void setVelocity (float newVelocity) { velocity = jlimit (newVelocity, 0.0f, 1.0f); }
/** Sets the pressure value that will be used for new notes. */
void setPressure (float newPressure) { pressure = jlimit (newPressure, 0.0f, 1.0f); }
/** Sets the note-off velocity, or "lift", value that will be used when notes are released. */
void setLift (float newLift) { lift = jlimit (newLift, 0.0f, 1.0f); }
/** Use this to enable the mouse source pressure to be used for the initial note-on
velocity, or "strike", value if the mouse source supports it.
*/
void setUseMouseSourcePressureForStrike (bool usePressureForStrike) { useMouseSourcePressureForStrike = usePressureForStrike; }
//==============================================================================
/** 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 = 0x1006000,
blackNoteColourId = 0x1006001,
textLabelColourId = 0x1006002,
noteCircleFillColourId = 0x1006003,
noteCircleOutlineColourId = 0x1006004
};
//==============================================================================
/** @internal */
void mouseDrag (const MouseEvent&) override;
/** @internal */
void mouseDown (const MouseEvent&) override;
/** @internal */
void mouseUp (const MouseEvent&) override;
/** @internal */
void focusLost (FocusChangeType) override;
/** @internal */
void colourChanged() override;
private:
//==========================================================================
struct MPENoteComponent;
//==============================================================================
void drawKeyboardBackground (Graphics& g, Rectangle<float> area) override;
void drawWhiteKey (int midiNoteNumber, Graphics& g, Rectangle<float> area) override;
void drawBlackKey (int midiNoteNumber, Graphics& g, Rectangle<float> area) override;
void updateNoteData (MPENote&);
void noteAdded (MPENote) override;
void notePressureChanged (MPENote) override;
void notePitchbendChanged (MPENote) override;
void noteTimbreChanged (MPENote) override;
void noteReleased (MPENote) override;
void zoneLayoutChanged() override;
void timerCallback() override;
//==============================================================================
MPEValue mousePositionToPitchbend (int, Point<float>);
MPEValue mousePositionToTimbre (Point<float>);
void addNewNote (MPENote);
void removeNote (MPENote);
void handleNoteOns (std::set<MPENote>&);
void handleNoteOffs (std::set<MPENote>&);
void updateNoteComponentBounds (const MPENote&, MPENoteComponent&);
void updateNoteComponents();
void updateZoneLayout();
//==============================================================================
MPEInstrument& instrument;
std::unique_ptr<MPEChannelAssigner> channelAssigner;
CriticalSection activeNotesLock;
std::vector<std::pair<MPENote, bool>> activeNotes;
std::vector<std::unique_ptr<MPENoteComponent>> noteComponents;
std::map<int, uint16> sourceIDMap;
float velocity = 0.7f, pressure = 1.0f, lift = 0.0f;
bool useMouseSourcePressureForStrike = false;
int perNotePitchbendRange = 48;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPEKeyboardComponent)
};
} // namespace juce

File diff suppressed because it is too large Load Diff

View File

@ -1,445 +1,279 @@
/*
==============================================================================
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
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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 KeyboardComponentBase,
private MidiKeyboardState::Listener,
private Timer
{
public:
//==============================================================================
/** 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; }
//==============================================================================
/** 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);
//==============================================================================
/** 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,
shadowColourId = 0x1005006
};
//==============================================================================
/** Use this method to draw a white note of the keyboard in a 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);
/** Use this method to draw a black note of the keyboard in a 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);
/** 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) { ignoreUnused (midiNoteNumber, e); return true; }
/** 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) { ignoreUnused (midiNoteNumber, e); return true; }
/** Callback when the mouse is released from a key.
@see mouseDownOnKey
*/
virtual void mouseUpOnKey (int midiNoteNumber, const MouseEvent& e) { ignoreUnused (midiNoteNumber, e); }
/** 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);
//==============================================================================
/** @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 timerCallback() override;
/** @internal */
bool keyStateChanged (bool isKeyDown) override;
/** @internal */
bool keyPressed (const KeyPress&) override;
/** @internal */
void focusLost (FocusChangeType) override;
/** @internal */
void colourChanged() override;
private:
//==============================================================================
void drawKeyboardBackground (Graphics& g, Rectangle<float> area) override final;
void drawWhiteKey (int midiNoteNumber, Graphics& g, Rectangle<float> area) override final;
void drawBlackKey (int midiNoteNumber, Graphics& g, Rectangle<float> area) override final;
void handleNoteOn (MidiKeyboardState*, int, int, float) override;
void handleNoteOff (MidiKeyboardState*, int, int, float) override;
//==============================================================================
void resetAnyKeysInUse();
void updateNoteUnderMouse (Point<float>, bool isDown, int fingerNum);
void updateNoteUnderMouse (const MouseEvent&, bool isDown);
void repaintNote (int midiNoteNumber);
//==============================================================================
MidiKeyboardState& state;
int midiChannel = 1, midiInChannelMask = 0xffff;
int keyMappingOctave = 6;
float velocity = 1.0f;
bool useMousePositionForVelocity = true;
Array<int> mouseOverNotes, mouseDownNotes;
Array<KeyPress> keyPresses;
Array<int> keyPressNotes;
BigInteger keysPressed, keysCurrentlyDrawnDown;
std::atomic<bool> noPendingUpdates { true };
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiKeyboardComponent)
};
} // namespace juce

View File

@ -1,102 +1,104 @@
/*
==============================================================================
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
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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_KeyboardComponentBase.cpp"
#include "gui/juce_MidiKeyboardComponent.cpp"
#include "gui/juce_MPEKeyboardComponent.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

View File

@ -1,89 +1,91 @@
/*
==============================================================================
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"
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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: 7.0.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_KeyboardComponentBase.h"
#include "gui/juce_MidiKeyboardComponent.h"
#include "gui/juce_MPEKeyboardComponent.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"

View File

@ -2,15 +2,15 @@
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
Copyright (c) 2022 - 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).
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-6-licence
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see

View File

@ -2,15 +2,15 @@
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
Copyright (c) 2022 - 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).
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-6-licence
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see

View File

@ -1,83 +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
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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

View File

@ -1,45 +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
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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

View File

@ -2,15 +2,15 @@
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
Copyright (c) 2022 - 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).
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-6-licence
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
@ -35,20 +35,19 @@ struct AudioTrackProducerClass : public ObjCClass<NSObject>
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");
addMethod (@selector (initWithAudioSourceHolder:), initWithAudioSourceHolder);
addMethod (@selector (verifyDataForTrack:intoBuffer:length:atAddress:blockSize:ioFlags:), produceDataForTrack);
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 (cleanupTrackAfterBurn:), cleanupTrackAfterBurn);
addMethod (@selector (cleanupTrackAfterVerification:), cleanupTrackAfterVerification);
addMethod (@selector (estimateLengthOfTrack:), estimateLengthOfTrack);
addMethod (@selector (prepareTrack:forBurn:toMedia:), prepareTrack);
addMethod (@selector (prepareTrackForVerification:), prepareTrackForVerification);
addMethod (@selector (produceDataForTrack:intoBuffer:length:atAddress:blockSize:ioFlags:),
produceDataForTrack, "I@:@^cIQI^I");
produceDataForTrack);
addMethod (@selector (producePreGapForTrack:intoBuffer:length:atAddress:blockSize:ioFlags:),
produceDataForTrack, "I@:@^cIQI^I");
produceDataForTrack);
registerClass();
}
@ -132,13 +131,9 @@ private:
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);
AudioData::interleaveSamples (AudioData::NonInterleavedSource<AudioData::Float32, AudioData::NativeEndian> { tempBuffer.getArrayOfReadPointers(), 2 },
AudioData::InterleavedDest<AudioData::Int16, AudioData::LittleEndian> { reinterpret_cast<uint16*> (buffer), 2 },
numSamples);
source->readPosition += numSamples;
}

View File

@ -2,15 +2,15 @@
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
Copyright (c) 2022 - 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).
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-6-licence
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see

View File

@ -2,15 +2,15 @@
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
Copyright (c) 2022 - 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).
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-6-licence
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
@ -26,10 +26,8 @@
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>
class API_AVAILABLE (macos (10.11)) BluetoothMidiPairingWindowClass : public ObjCClass<NSObject>
{
public:
struct Callbacks
@ -44,12 +42,12 @@ public:
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");
addMethod (@selector (initWithCallbacks:), initWithCallbacks);
addMethod (@selector (show:), show);
addMethod (@selector (receivedWindowWillClose:), receivedWindowWillClose);
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
addMethod (@selector (dealloc), dealloc, "v@:");
addMethod (@selector (dealloc), dealloc);
registerClass();
}
@ -119,7 +117,7 @@ private:
}
};
class BluetoothMidiSelectorWindowHelper : public DeletedAtShutdown
class API_AVAILABLE (macos (10.11)) BluetoothMidiSelectorWindowHelper : public DeletedAtShutdown
{
public:
BluetoothMidiSelectorWindowHelper (ModalComponentManager::Callback* exitCallback,
@ -159,20 +157,12 @@ private:
bool BluetoothMidiDevicePairingDialogue::open (ModalComponentManager::Callback* exitCallback,
Rectangle<int>* bounds)
{
new BluetoothMidiSelectorWindowHelper (exitCallback, bounds);
return true;
}
if (@available (macOS 10.11, *))
{
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
@ -183,9 +173,10 @@ bool BluetoothMidiDevicePairingDialogue::open (ModalComponentManager::Callback*
bool BluetoothMidiDevicePairingDialogue::isAvailable()
{
if (@available (macOS 10.11, *))
return true;
return false;
}
#endif
} // namespace juce

View File

@ -1,417 +1,410 @@
/*
==============================================================================
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
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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);
AudioData::interleaveSamples (AudioData::NonInterleavedSource<AudioData::Float32, AudioData::NativeEndian> { sourceBuffer.getArrayOfReadPointers(), 2 },
AudioData::InterleavedDest<AudioData::Int16, Audiodata::LittleEndian> { reinterpret_cast<uint16*> (buffer), 2 },
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

File diff suppressed because it is too large Load Diff

View File

@ -1,45 +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
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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

View File

@ -1,436 +1,482 @@
/*
==============================================================================
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
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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;
sampleCount = 0;
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::audioDeviceIOCallbackWithContext (const float** const inputChannelData,
const int numInputChannels,
float** const outputChannelData,
const int numOutputChannels,
const int numSamples,
const AudioIODeviceCallbackContext& context)
{
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());
class PlayHead : private AudioPlayHead
{
public:
PlayHead (AudioProcessor& proc,
Optional<uint64_t> hostTimeIn,
uint64_t sampleCountIn,
double sampleRateIn)
: processor (proc),
hostTimeNs (hostTimeIn),
sampleCount (sampleCountIn),
seconds ((double) sampleCountIn / sampleRateIn)
{
processor.setPlayHead (this);
}
~PlayHead() override
{
processor.setPlayHead (nullptr);
}
private:
Optional<PositionInfo> getPosition() const override
{
PositionInfo info;
info.setHostTimeNs (hostTimeNs);
info.setTimeInSamples ((int64_t) sampleCount);
info.setTimeInSeconds (seconds);
return info;
}
AudioProcessor& processor;
Optional<uint64_t> hostTimeNs;
uint64_t sampleCount;
double seconds;
};
PlayHead playHead { *processor,
context.hostTimeNs != nullptr ? makeOptional (*context.hostTimeNs) : nullopt,
sampleCount,
sampleRate };
sampleCount += (uint64_t) numSamples;
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

View File

@ -1,144 +1,145 @@
/*
==============================================================================
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
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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 audioDeviceIOCallbackWithContext (const float**, int, float**, int, int, const AudioIODeviceCallbackContext&) 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;
uint64_t sampleCount = 0;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioProcessorPlayer)
};
} // namespace juce

View File

@ -1,306 +1,277 @@
/*
==============================================================================
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
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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)
{}
//==============================================================================
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 prepareToPlay (int, double) override {}
void releaseResources() override {}
void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override
{
bufferToFill.clearActiveBufferRegion();
const int bufferSize = buffer->getNumSamples();
const int samplesNeeded = bufferToFill.numSamples;
const int samplesToCopy = jmin (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 += samplesNeeded;
if (looping)
position %= bufferSize;
}
private:
//==============================================================================
OptionalScopedPointer<AudioBuffer<float>> buffer;
int position = 0;
bool looping = false, playAcrossAllChannels;
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

View File

@ -1,136 +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
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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