migrating to the latest JUCE version
This commit is contained in:
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
@ -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
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
462
deps/juce/modules/juce_audio_utils/gui/juce_KeyboardComponentBase.cpp
vendored
Normal file
462
deps/juce/modules/juce_audio_utils/gui/juce_KeyboardComponentBase.cpp
vendored
Normal 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
|
295
deps/juce/modules/juce_audio_utils/gui/juce_KeyboardComponentBase.h
vendored
Normal file
295
deps/juce/modules/juce_audio_utils/gui/juce_KeyboardComponentBase.h
vendored
Normal 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
|
507
deps/juce/modules/juce_audio_utils/gui/juce_MPEKeyboardComponent.cpp
vendored
Normal file
507
deps/juce/modules/juce_audio_utils/gui/juce_MPEKeyboardComponent.cpp
vendored
Normal 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 = [¬esToUpdate] (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
|
153
deps/juce/modules/juce_audio_utils/gui/juce_MPEKeyboardComponent.h
vendored
Normal file
153
deps/juce/modules/juce_audio_utils/gui/juce_MPEKeyboardComponent.h
vendored
Normal 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
@ -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
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user