build(deps): vendor JUCE 7.0.12
This commit is contained in:
+773
@@ -0,0 +1,773 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE examples.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE 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 PIP. A PIP is a short snippet
|
||||
of code that can be read by the Projucer and used to generate a JUCE project.
|
||||
|
||||
BEGIN_JUCE_PIP_METADATA
|
||||
|
||||
name: MPEDemo
|
||||
version: 1.0.0
|
||||
vendor: JUCE
|
||||
website: http://juce.com
|
||||
description: Simple MPE synthesiser application.
|
||||
|
||||
dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats,
|
||||
juce_audio_processors, juce_audio_utils, juce_core,
|
||||
juce_data_structures, juce_events, juce_graphics,
|
||||
juce_gui_basics, juce_gui_extra
|
||||
exporters: xcode_mac, vs2022, linux_make, androidstudio, xcode_iphone
|
||||
|
||||
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
|
||||
|
||||
type: Component
|
||||
mainClass: MPEDemo
|
||||
|
||||
useLocalCopy: 1
|
||||
|
||||
END_JUCE_PIP_METADATA
|
||||
|
||||
*******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
//==============================================================================
|
||||
class ZoneColourPicker
|
||||
{
|
||||
public:
|
||||
ZoneColourPicker() {}
|
||||
|
||||
//==============================================================================
|
||||
Colour getColourForMidiChannel (int midiChannel) noexcept
|
||||
{
|
||||
if (legacyModeEnabled)
|
||||
return Colours::white;
|
||||
|
||||
if (zoneLayout.getLowerZone().isUsingChannelAsMemberChannel (midiChannel))
|
||||
return getColourForZone (true);
|
||||
|
||||
if (zoneLayout.getUpperZone().isUsingChannelAsMemberChannel (midiChannel))
|
||||
return getColourForZone (false);
|
||||
|
||||
return Colours::transparentBlack;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Colour getColourForZone (bool isLowerZone) const noexcept
|
||||
{
|
||||
if (legacyModeEnabled)
|
||||
return Colours::white;
|
||||
|
||||
if (isLowerZone)
|
||||
return Colours::blue;
|
||||
|
||||
return Colours::red;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void setZoneLayout (MPEZoneLayout layout) noexcept { zoneLayout = layout; }
|
||||
void setLegacyModeEnabled (bool shouldBeEnabled) noexcept { legacyModeEnabled = shouldBeEnabled; }
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
MPEZoneLayout zoneLayout;
|
||||
bool legacyModeEnabled = false;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ZoneColourPicker)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class MPESetupComponent final : public Component
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
MPESetupComponent (MPEInstrument& instr)
|
||||
: instrument (instr)
|
||||
{
|
||||
addAndMakeVisible (isLowerZoneButton);
|
||||
isLowerZoneButton.setToggleState (true, NotificationType::dontSendNotification);
|
||||
|
||||
initialiseComboBoxWithConsecutiveIntegers (memberChannels, memberChannelsLabel, 0, 16, defaultMemberChannels);
|
||||
initialiseComboBoxWithConsecutiveIntegers (masterPitchbendRange, masterPitchbendRangeLabel, 0, 96, defaultMasterPitchbendRange);
|
||||
initialiseComboBoxWithConsecutiveIntegers (notePitchbendRange, notePitchbendRangeLabel, 0, 96, defaultNotePitchbendRange);
|
||||
|
||||
initialiseComboBoxWithConsecutiveIntegers (legacyStartChannel, legacyStartChannelLabel, 1, 16, 1, false);
|
||||
initialiseComboBoxWithConsecutiveIntegers (legacyEndChannel, legacyEndChannelLabel, 1, 16, 16, false);
|
||||
initialiseComboBoxWithConsecutiveIntegers (legacyPitchbendRange, legacyPitchbendRangeLabel, 0, 96, 2, false);
|
||||
|
||||
addAndMakeVisible (setZoneButton);
|
||||
setZoneButton.onClick = [this] { setZoneButtonClicked(); };
|
||||
|
||||
addAndMakeVisible (clearAllZonesButton);
|
||||
clearAllZonesButton.onClick = [this] { clearAllZonesButtonClicked(); };
|
||||
|
||||
addAndMakeVisible (legacyModeEnabledToggle);
|
||||
legacyModeEnabledToggle.onClick = [this] { legacyModeEnabledToggleClicked(); };
|
||||
|
||||
addAndMakeVisible (voiceStealingEnabledToggle);
|
||||
voiceStealingEnabledToggle.onClick = [this] { voiceStealingEnabledToggleClicked(); };
|
||||
|
||||
initialiseComboBoxWithConsecutiveIntegers (numberOfVoices, numberOfVoicesLabel, 1, 20, 15);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void resized() override
|
||||
{
|
||||
Rectangle<int> r (proportionOfWidth (0.65f), 15, proportionOfWidth (0.25f), 3000);
|
||||
auto h = 24;
|
||||
auto hspace = 6;
|
||||
auto hbigspace = 18;
|
||||
|
||||
isLowerZoneButton.setBounds (r.removeFromTop (h));
|
||||
r.removeFromTop (hspace);
|
||||
memberChannels.setBounds (r.removeFromTop (h));
|
||||
r.removeFromTop (hspace);
|
||||
notePitchbendRange.setBounds (r.removeFromTop (h));
|
||||
r.removeFromTop (hspace);
|
||||
masterPitchbendRange.setBounds (r.removeFromTop (h));
|
||||
|
||||
legacyStartChannel .setBounds (isLowerZoneButton .getBounds());
|
||||
legacyEndChannel .setBounds (memberChannels .getBounds());
|
||||
legacyPitchbendRange.setBounds (notePitchbendRange.getBounds());
|
||||
|
||||
r.removeFromTop (hbigspace);
|
||||
|
||||
auto buttonLeft = proportionOfWidth (0.5f);
|
||||
|
||||
setZoneButton.setBounds (r.removeFromTop (h).withLeft (buttonLeft));
|
||||
r.removeFromTop (hspace);
|
||||
clearAllZonesButton.setBounds (r.removeFromTop (h).withLeft (buttonLeft));
|
||||
|
||||
r.removeFromTop (hbigspace);
|
||||
|
||||
auto toggleLeft = proportionOfWidth (0.25f);
|
||||
|
||||
legacyModeEnabledToggle.setBounds (r.removeFromTop (h).withLeft (toggleLeft));
|
||||
r.removeFromTop (hspace);
|
||||
voiceStealingEnabledToggle.setBounds (r.removeFromTop (h).withLeft (toggleLeft));
|
||||
r.removeFromTop (hspace);
|
||||
numberOfVoices.setBounds (r.removeFromTop (h));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool isVoiceStealingEnabled() const { return voiceStealingEnabledToggle.getToggleState(); }
|
||||
int getNumVoices() const { return numberOfVoices.getText().getIntValue(); }
|
||||
|
||||
std::function<void()> onSynthParametersChange;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
void initialiseComboBoxWithConsecutiveIntegers (ComboBox& comboBox, Label& labelToAttach,
|
||||
int firstValue, int numValues, int valueToSelect,
|
||||
bool makeVisible = true)
|
||||
{
|
||||
for (auto i = 0; i < numValues; ++i)
|
||||
comboBox.addItem (String (i + firstValue), i + 1);
|
||||
|
||||
comboBox.setSelectedId (valueToSelect - firstValue + 1);
|
||||
labelToAttach.attachToComponent (&comboBox, true);
|
||||
|
||||
if (makeVisible)
|
||||
addAndMakeVisible (comboBox);
|
||||
else
|
||||
addChildComponent (comboBox);
|
||||
|
||||
if (&comboBox == &numberOfVoices)
|
||||
comboBox.onChange = [this] { numberOfVoicesChanged(); };
|
||||
else if (&comboBox == &legacyPitchbendRange)
|
||||
comboBox.onChange = [this] { if (legacyModeEnabledToggle.getToggleState()) legacyModePitchbendRangeChanged(); };
|
||||
else if (&comboBox == &legacyStartChannel || &comboBox == &legacyEndChannel)
|
||||
comboBox.onChange = [this] { if (legacyModeEnabledToggle.getToggleState()) legacyModeChannelRangeChanged(); };
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void setZoneButtonClicked()
|
||||
{
|
||||
auto isLowerZone = isLowerZoneButton.getToggleState();
|
||||
auto numMemberChannels = memberChannels.getText().getIntValue();
|
||||
auto perNotePb = notePitchbendRange.getText().getIntValue();
|
||||
auto masterPb = masterPitchbendRange.getText().getIntValue();
|
||||
|
||||
auto zoneLayout = instrument.getZoneLayout();
|
||||
|
||||
if (isLowerZone)
|
||||
zoneLayout.setLowerZone (numMemberChannels, perNotePb, masterPb);
|
||||
else
|
||||
zoneLayout.setUpperZone (numMemberChannels, perNotePb, masterPb);
|
||||
|
||||
instrument.setZoneLayout (zoneLayout);
|
||||
}
|
||||
|
||||
void clearAllZonesButtonClicked()
|
||||
{
|
||||
instrument.setZoneLayout ({});
|
||||
}
|
||||
|
||||
void legacyModeEnabledToggleClicked()
|
||||
{
|
||||
auto legacyModeEnabled = legacyModeEnabledToggle.getToggleState();
|
||||
|
||||
isLowerZoneButton .setVisible (! legacyModeEnabled);
|
||||
memberChannels .setVisible (! legacyModeEnabled);
|
||||
notePitchbendRange .setVisible (! legacyModeEnabled);
|
||||
masterPitchbendRange.setVisible (! legacyModeEnabled);
|
||||
setZoneButton .setVisible (! legacyModeEnabled);
|
||||
clearAllZonesButton .setVisible (! legacyModeEnabled);
|
||||
|
||||
legacyStartChannel .setVisible (legacyModeEnabled);
|
||||
legacyEndChannel .setVisible (legacyModeEnabled);
|
||||
legacyPitchbendRange.setVisible (legacyModeEnabled);
|
||||
|
||||
if (legacyModeEnabled)
|
||||
{
|
||||
if (areLegacyModeParametersValid())
|
||||
{
|
||||
instrument.enableLegacyMode();
|
||||
|
||||
instrument.setLegacyModeChannelRange (getLegacyModeChannelRange());
|
||||
instrument.setLegacyModePitchbendRange (getLegacyModePitchbendRange());
|
||||
}
|
||||
else
|
||||
{
|
||||
handleInvalidLegacyModeParameters();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
instrument.setZoneLayout ({ MPEZone (MPEZone::Type::lower, 15) });
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void legacyModePitchbendRangeChanged()
|
||||
{
|
||||
jassert (legacyModeEnabledToggle.getToggleState() == true);
|
||||
|
||||
instrument.setLegacyModePitchbendRange (getLegacyModePitchbendRange());
|
||||
}
|
||||
|
||||
void legacyModeChannelRangeChanged()
|
||||
{
|
||||
jassert (legacyModeEnabledToggle.getToggleState() == true);
|
||||
|
||||
if (areLegacyModeParametersValid())
|
||||
instrument.setLegacyModeChannelRange (getLegacyModeChannelRange());
|
||||
else
|
||||
handleInvalidLegacyModeParameters();
|
||||
}
|
||||
|
||||
bool areLegacyModeParametersValid() const
|
||||
{
|
||||
return legacyStartChannel.getText().getIntValue() <= legacyEndChannel.getText().getIntValue();
|
||||
}
|
||||
|
||||
void handleInvalidLegacyModeParameters()
|
||||
{
|
||||
auto options = MessageBoxOptions::makeOptionsOk (MessageBoxIconType::WarningIcon,
|
||||
"Invalid legacy mode channel layout",
|
||||
"Cannot set legacy mode start/end channel:\n"
|
||||
"The end channel must not be less than the start channel!",
|
||||
"Got it");
|
||||
messageBox = AlertWindow::showScopedAsync (options, nullptr);
|
||||
}
|
||||
|
||||
Range<int> getLegacyModeChannelRange() const
|
||||
{
|
||||
return { legacyStartChannel.getText().getIntValue(),
|
||||
legacyEndChannel.getText().getIntValue() + 1 };
|
||||
}
|
||||
|
||||
int getLegacyModePitchbendRange() const
|
||||
{
|
||||
return legacyPitchbendRange.getText().getIntValue();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void voiceStealingEnabledToggleClicked()
|
||||
{
|
||||
jassert (onSynthParametersChange != nullptr);
|
||||
onSynthParametersChange();
|
||||
}
|
||||
|
||||
void numberOfVoicesChanged()
|
||||
{
|
||||
jassert (onSynthParametersChange != nullptr);
|
||||
onSynthParametersChange();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
MPEInstrument& instrument;
|
||||
|
||||
ComboBox memberChannels, masterPitchbendRange, notePitchbendRange;
|
||||
|
||||
ToggleButton isLowerZoneButton { "Lower zone" };
|
||||
|
||||
Label memberChannelsLabel { {}, "Nr. of member channels:" };
|
||||
Label masterPitchbendRangeLabel { {}, "Master pitchbend range (semitones):" };
|
||||
Label notePitchbendRangeLabel { {}, "Note pitchbend range (semitones):" };
|
||||
|
||||
TextButton setZoneButton { "Set zone" };
|
||||
TextButton clearAllZonesButton { "Clear all zones" };
|
||||
|
||||
ComboBox legacyStartChannel, legacyEndChannel, legacyPitchbendRange;
|
||||
|
||||
Label legacyStartChannelLabel { {}, "First channel:" };
|
||||
Label legacyEndChannelLabel { {}, "Last channel:" };
|
||||
Label legacyPitchbendRangeLabel { {}, "Pitchbend range (semitones):"};
|
||||
|
||||
ToggleButton legacyModeEnabledToggle { "Enable Legacy Mode" };
|
||||
ToggleButton voiceStealingEnabledToggle { "Enable synth voice stealing" };
|
||||
|
||||
ComboBox numberOfVoices;
|
||||
Label numberOfVoicesLabel { {}, "Number of synth voices"};
|
||||
|
||||
ScopedMessageBox messageBox;
|
||||
|
||||
static constexpr int defaultMemberChannels = 15,
|
||||
defaultMasterPitchbendRange = 2,
|
||||
defaultNotePitchbendRange = 48;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESetupComponent)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class ZoneLayoutComponent final : public Component,
|
||||
private MPEInstrument::Listener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
ZoneLayoutComponent (MPEInstrument& instr, ZoneColourPicker& zoneColourPicker)
|
||||
: instrument (instr),
|
||||
colourPicker (zoneColourPicker)
|
||||
{
|
||||
instrument.addListener (this);
|
||||
}
|
||||
|
||||
~ZoneLayoutComponent() override
|
||||
{
|
||||
instrument.removeListener (this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
paintBackground (g);
|
||||
|
||||
if (instrument.isLegacyModeEnabled())
|
||||
paintLegacyMode (g);
|
||||
else
|
||||
paintZones (g);
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
void zoneLayoutChanged() override
|
||||
{
|
||||
repaint();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void paintBackground (Graphics& g)
|
||||
{
|
||||
g.setColour (Colours::black);
|
||||
auto channelWidth = getChannelRectangleWidth();
|
||||
|
||||
for (auto i = 0; i < numMidiChannels; ++i)
|
||||
{
|
||||
auto x = float (i) * channelWidth;
|
||||
Rectangle<int> channelArea ((int) x, 0, (int) channelWidth, getHeight());
|
||||
|
||||
g.drawLine ({ x, 0.0f, x, float (getHeight()) });
|
||||
g.drawText (String (i + 1), channelArea.reduced (4, 4), Justification::topLeft, false);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void paintZones (Graphics& g)
|
||||
{
|
||||
auto channelWidth = getChannelRectangleWidth();
|
||||
|
||||
auto zoneLayout = instrument.getZoneLayout();
|
||||
|
||||
Array<MPEZoneLayout::Zone> activeZones;
|
||||
if (zoneLayout.getLowerZone().isActive()) activeZones.add (zoneLayout.getLowerZone());
|
||||
if (zoneLayout.getUpperZone().isActive()) activeZones.add (zoneLayout.getUpperZone());
|
||||
|
||||
for (auto zone : activeZones)
|
||||
{
|
||||
auto zoneColour = colourPicker.getColourForZone (zone.isLowerZone());
|
||||
|
||||
auto xPos = zone.isLowerZone() ? 0 : zone.getLastMemberChannel() - 1;
|
||||
|
||||
Rectangle<int> zoneRect { int (channelWidth * (float) xPos), 20,
|
||||
int (channelWidth * (float) (zone.numMemberChannels + 1)), getHeight() - 20 };
|
||||
|
||||
g.setColour (zoneColour);
|
||||
g.drawRect (zoneRect, 3);
|
||||
|
||||
auto masterRect = zone.isLowerZone() ? zoneRect.removeFromLeft ((int) channelWidth) : zoneRect.removeFromRight ((int) channelWidth);
|
||||
|
||||
g.setColour (zoneColour.withAlpha (0.3f));
|
||||
g.fillRect (masterRect);
|
||||
|
||||
g.setColour (zoneColour.contrasting());
|
||||
g.drawText ("<>" + String (zone.masterPitchbendRange), masterRect.reduced (4), Justification::top, false);
|
||||
g.drawText ("<>" + String (zone.perNotePitchbendRange), masterRect.reduced (4), Justification::bottom, false);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void paintLegacyMode (Graphics& g)
|
||||
{
|
||||
auto channelRange = instrument.getLegacyModeChannelRange();
|
||||
auto startChannel = channelRange.getStart() - 1;
|
||||
auto numChannels = channelRange.getEnd() - startChannel - 1;
|
||||
|
||||
Rectangle<int> zoneRect (int (getChannelRectangleWidth() * (float) startChannel), 0,
|
||||
int (getChannelRectangleWidth() * (float) numChannels), getHeight());
|
||||
|
||||
zoneRect.removeFromTop (20);
|
||||
|
||||
g.setColour (Colours::white);
|
||||
g.drawRect (zoneRect, 3);
|
||||
g.drawText ("LGCY", zoneRect.reduced (4, 4), Justification::topLeft, false);
|
||||
g.drawText ("<>" + String (instrument.getLegacyModePitchbendRange()), zoneRect.reduced (4, 4), Justification::bottomLeft, false);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
float getChannelRectangleWidth() const noexcept
|
||||
{
|
||||
return (float) getWidth() / (float) numMidiChannels;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static constexpr int numMidiChannels = 16;
|
||||
|
||||
MPEInstrument& instrument;
|
||||
ZoneColourPicker& colourPicker;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class MPEDemoSynthVoice final : public MPESynthesiserVoice
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
MPEDemoSynthVoice() {}
|
||||
|
||||
//==============================================================================
|
||||
void noteStarted() override
|
||||
{
|
||||
jassert (currentlyPlayingNote.isValid());
|
||||
jassert (currentlyPlayingNote.keyState == MPENote::keyDown
|
||||
|| currentlyPlayingNote.keyState == MPENote::keyDownAndSustained);
|
||||
|
||||
level .setTargetValue (currentlyPlayingNote.pressure.asUnsignedFloat());
|
||||
frequency.setTargetValue (currentlyPlayingNote.getFrequencyInHertz());
|
||||
timbre .setTargetValue (currentlyPlayingNote.timbre.asUnsignedFloat());
|
||||
|
||||
phase = 0.0;
|
||||
auto cyclesPerSample = frequency.getNextValue() / currentSampleRate;
|
||||
phaseDelta = MathConstants<double>::twoPi * cyclesPerSample;
|
||||
|
||||
tailOff = 0.0;
|
||||
}
|
||||
|
||||
void noteStopped (bool allowTailOff) override
|
||||
{
|
||||
jassert (currentlyPlayingNote.keyState == MPENote::off);
|
||||
|
||||
if (allowTailOff)
|
||||
{
|
||||
// start a tail-off by setting this flag. The render callback will pick up on
|
||||
// this and do a fade out, calling clearCurrentNote() when it's finished.
|
||||
|
||||
if (approximatelyEqual (tailOff, 0.0)) // we only need to begin a tail-off if it's not already doing so - the
|
||||
// stopNote method could be called more than once.
|
||||
tailOff = 1.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// we're being told to stop playing immediately, so reset everything..
|
||||
clearCurrentNote();
|
||||
phaseDelta = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
void notePressureChanged() override
|
||||
{
|
||||
level.setTargetValue (currentlyPlayingNote.pressure.asUnsignedFloat());
|
||||
}
|
||||
|
||||
void notePitchbendChanged() override
|
||||
{
|
||||
frequency.setTargetValue (currentlyPlayingNote.getFrequencyInHertz());
|
||||
}
|
||||
|
||||
void noteTimbreChanged() override
|
||||
{
|
||||
timbre.setTargetValue (currentlyPlayingNote.timbre.asUnsignedFloat());
|
||||
}
|
||||
|
||||
void noteKeyStateChanged() override {}
|
||||
|
||||
void setCurrentSampleRate (double newRate) override
|
||||
{
|
||||
if (! approximatelyEqual (currentSampleRate, newRate))
|
||||
{
|
||||
noteStopped (false);
|
||||
currentSampleRate = newRate;
|
||||
|
||||
level .reset (currentSampleRate, smoothingLengthInSeconds);
|
||||
timbre .reset (currentSampleRate, smoothingLengthInSeconds);
|
||||
frequency.reset (currentSampleRate, smoothingLengthInSeconds);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
virtual void renderNextBlock (AudioBuffer<float>& outputBuffer,
|
||||
int startSample,
|
||||
int numSamples) override
|
||||
{
|
||||
if (! approximatelyEqual (phaseDelta, 0.0))
|
||||
{
|
||||
if (tailOff > 0.0)
|
||||
{
|
||||
while (--numSamples >= 0)
|
||||
{
|
||||
auto currentSample = getNextSample() * (float) tailOff;
|
||||
|
||||
for (auto i = outputBuffer.getNumChannels(); --i >= 0;)
|
||||
outputBuffer.addSample (i, startSample, currentSample);
|
||||
|
||||
++startSample;
|
||||
|
||||
tailOff *= 0.99;
|
||||
|
||||
if (tailOff <= 0.005)
|
||||
{
|
||||
clearCurrentNote();
|
||||
|
||||
phaseDelta = 0.0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while (--numSamples >= 0)
|
||||
{
|
||||
auto currentSample = getNextSample();
|
||||
|
||||
for (auto i = outputBuffer.getNumChannels(); --i >= 0;)
|
||||
outputBuffer.addSample (i, startSample, currentSample);
|
||||
|
||||
++startSample;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
using MPESynthesiserVoice::renderNextBlock;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
float getNextSample() noexcept
|
||||
{
|
||||
auto levelDb = (level.getNextValue() - 1.0) * maxLevelDb;
|
||||
auto amplitude = pow (10.0f, 0.05f * levelDb) * maxLevel;
|
||||
|
||||
// timbre is used to blend between a sine and a square.
|
||||
auto f1 = std::sin (phase);
|
||||
auto f2 = copysign (1.0, f1);
|
||||
auto a2 = timbre.getNextValue();
|
||||
auto a1 = 1.0 - a2;
|
||||
|
||||
auto nextSample = float (amplitude * ((a1 * f1) + (a2 * f2)));
|
||||
|
||||
auto cyclesPerSample = frequency.getNextValue() / currentSampleRate;
|
||||
phaseDelta = MathConstants<double>::twoPi * cyclesPerSample;
|
||||
phase = std::fmod (phase + phaseDelta, MathConstants<double>::twoPi);
|
||||
|
||||
return nextSample;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
SmoothedValue<double> level, timbre, frequency;
|
||||
|
||||
double phase = 0.0;
|
||||
double phaseDelta = 0.0;
|
||||
double tailOff = 0.0;
|
||||
|
||||
const double maxLevel = 0.05;
|
||||
const double maxLevelDb = 31.0;
|
||||
const double smoothingLengthInSeconds = 0.01;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class MPEDemo final : public Component,
|
||||
private AudioIODeviceCallback,
|
||||
private MidiInputCallback,
|
||||
private MPEInstrument::Listener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
MPEDemo()
|
||||
{
|
||||
#ifndef JUCE_DEMO_RUNNER
|
||||
audioDeviceManager.initialise (0, 2, nullptr, true, {}, nullptr);
|
||||
#endif
|
||||
|
||||
audioDeviceManager.addMidiInputDeviceCallback ({}, this);
|
||||
audioDeviceManager.addAudioCallback (this);
|
||||
|
||||
addAndMakeVisible (audioSetupComp);
|
||||
addAndMakeVisible (mpeSetupComp);
|
||||
addAndMakeVisible (zoneLayoutComp);
|
||||
addAndMakeVisible (keyboardComponent);
|
||||
|
||||
synth.setVoiceStealingEnabled (false);
|
||||
for (auto i = 0; i < 15; ++i)
|
||||
synth.addVoice (new MPEDemoSynthVoice());
|
||||
|
||||
mpeSetupComp.onSynthParametersChange = [this]
|
||||
{
|
||||
synth.setVoiceStealingEnabled (mpeSetupComp.isVoiceStealingEnabled());
|
||||
|
||||
auto numVoices = mpeSetupComp.getNumVoices();
|
||||
|
||||
if (numVoices < synth.getNumVoices())
|
||||
{
|
||||
synth.reduceNumVoices (numVoices);
|
||||
}
|
||||
else
|
||||
{
|
||||
while (synth.getNumVoices() < numVoices)
|
||||
synth.addVoice (new MPEDemoSynthVoice());
|
||||
}
|
||||
};
|
||||
|
||||
instrument.addListener (this);
|
||||
|
||||
setSize (880, 720);
|
||||
}
|
||||
|
||||
~MPEDemo() override
|
||||
{
|
||||
audioDeviceManager.removeMidiInputDeviceCallback ({}, this);
|
||||
audioDeviceManager.removeAudioCallback (this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void resized() override
|
||||
{
|
||||
auto zoneLayoutCompHeight = 60;
|
||||
auto audioSetupCompRelativeWidth = 0.55f;
|
||||
|
||||
auto r = getLocalBounds();
|
||||
|
||||
keyboardComponent.setBounds (r.removeFromBottom (150));
|
||||
r.reduce (10, 10);
|
||||
|
||||
zoneLayoutComp.setBounds (r.removeFromBottom (zoneLayoutCompHeight));
|
||||
audioSetupComp.setBounds (r.removeFromLeft (proportionOfWidth (audioSetupCompRelativeWidth)));
|
||||
mpeSetupComp .setBounds (r);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void audioDeviceIOCallbackWithContext (const float* const* inputChannelData, int numInputChannels,
|
||||
float* const* outputChannelData, int numOutputChannels,
|
||||
int numSamples, const AudioIODeviceCallbackContext& context) override
|
||||
{
|
||||
ignoreUnused (inputChannelData, numInputChannels, context);
|
||||
|
||||
AudioBuffer<float> buffer (outputChannelData, numOutputChannels, numSamples);
|
||||
buffer.clear();
|
||||
|
||||
MidiBuffer incomingMidi;
|
||||
midiCollector.removeNextBlockOfMessages (incomingMidi, numSamples);
|
||||
synth.renderNextBlock (buffer, incomingMidi, 0, numSamples);
|
||||
}
|
||||
|
||||
void audioDeviceAboutToStart (AudioIODevice* device) override
|
||||
{
|
||||
auto sampleRate = device->getCurrentSampleRate();
|
||||
midiCollector.reset (sampleRate);
|
||||
synth.setCurrentPlaybackSampleRate (sampleRate);
|
||||
}
|
||||
|
||||
void audioDeviceStopped() override {}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
void handleIncomingMidiMessage (MidiInput* /*source*/,
|
||||
const MidiMessage& message) override
|
||||
{
|
||||
instrument.processNextMidiEvent (message);
|
||||
midiCollector.addMessageToQueue (message);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void zoneLayoutChanged() override
|
||||
{
|
||||
if (instrument.isLegacyModeEnabled())
|
||||
{
|
||||
colourPicker.setLegacyModeEnabled (true);
|
||||
|
||||
synth.enableLegacyMode (instrument.getLegacyModePitchbendRange(),
|
||||
instrument.getLegacyModeChannelRange());
|
||||
}
|
||||
else
|
||||
{
|
||||
colourPicker.setLegacyModeEnabled (false);
|
||||
|
||||
auto zoneLayout = instrument.getZoneLayout();
|
||||
|
||||
if (auto* midiOutput = audioDeviceManager.getDefaultMidiOutput())
|
||||
midiOutput->sendBlockOfMessagesNow (MPEMessages::setZoneLayout (zoneLayout));
|
||||
|
||||
synth.setZoneLayout (zoneLayout);
|
||||
colourPicker.setZoneLayout (zoneLayout);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// if this PIP is running inside the demo runner, we'll use the shared device manager instead
|
||||
#ifndef JUCE_DEMO_RUNNER
|
||||
AudioDeviceManager audioDeviceManager;
|
||||
#else
|
||||
AudioDeviceManager& audioDeviceManager { getSharedAudioDeviceManager (0, 2) };
|
||||
#endif
|
||||
|
||||
AudioDeviceSelectorComponent audioSetupComp { audioDeviceManager, 0, 0, 0, 256, true, true, true, false };
|
||||
MidiMessageCollector midiCollector;
|
||||
|
||||
MPEInstrument instrument { MPEZone (MPEZone::Type::lower, 15) };
|
||||
|
||||
ZoneColourPicker colourPicker;
|
||||
MPESetupComponent mpeSetupComp { instrument };
|
||||
ZoneLayoutComponent zoneLayoutComp { instrument, colourPicker};
|
||||
|
||||
MPESynthesiser synth { instrument };
|
||||
MPEKeyboardComponent keyboardComponent { instrument, MPEKeyboardComponent::horizontalKeyboard };
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPEDemo)
|
||||
};
|
||||
Reference in New Issue
Block a user