1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00
JUCE/examples/Demo/Source/Demos/AudioSynthesiserDemo.cpp

315 lines
12 KiB
C++

/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2015 - ROLI Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#include "../JuceDemoHeader.h"
#include "AudioLiveScrollingDisplay.h"
//==============================================================================
/** Our demo synth sound is just a basic sine wave.. */
struct SineWaveSound : public SynthesiserSound
{
SineWaveSound() {}
bool appliesToNote (int /*midiNoteNumber*/) override { return true; }
bool appliesToChannel (int /*midiChannel*/) override { return true; }
};
//==============================================================================
/** Our demo synth voice just plays a sine wave.. */
struct SineWaveVoice : public SynthesiserVoice
{
SineWaveVoice() : currentAngle (0), angleDelta (0), level (0), tailOff (0)
{
}
bool canPlaySound (SynthesiserSound* sound) override
{
return dynamic_cast<SineWaveSound*> (sound) != nullptr;
}
void startNote (int midiNoteNumber, float velocity,
SynthesiserSound*, int /*currentPitchWheelPosition*/) override
{
currentAngle = 0.0;
level = velocity * 0.15;
tailOff = 0.0;
double cyclesPerSecond = MidiMessage::getMidiNoteInHertz (midiNoteNumber);
double cyclesPerSample = cyclesPerSecond / getSampleRate();
angleDelta = cyclesPerSample * 2.0 * double_Pi;
}
void stopNote (float /*velocity*/, bool allowTailOff) override
{
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 (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();
angleDelta = 0.0;
}
}
void pitchWheelMoved (int /*newValue*/) override
{
// can't be bothered implementing this for the demo!
}
void controllerMoved (int /*controllerNumber*/, int /*newValue*/) override
{
// not interested in controllers in this case.
}
void renderNextBlock (AudioSampleBuffer& outputBuffer, int startSample, int numSamples) override
{
if (angleDelta != 0.0)
{
if (tailOff > 0)
{
while (--numSamples >= 0)
{
const float currentSample = (float) (std::sin (currentAngle) * level * tailOff);
for (int i = outputBuffer.getNumChannels(); --i >= 0;)
outputBuffer.addSample (i, startSample, currentSample);
currentAngle += angleDelta;
++startSample;
tailOff *= 0.99;
if (tailOff <= 0.005)
{
clearCurrentNote();
angleDelta = 0.0;
break;
}
}
}
else
{
while (--numSamples >= 0)
{
const float currentSample = (float) (std::sin (currentAngle) * level);
for (int i = outputBuffer.getNumChannels(); --i >= 0;)
outputBuffer.addSample (i, startSample, currentSample);
currentAngle += angleDelta;
++startSample;
}
}
}
}
private:
double currentAngle, angleDelta, level, tailOff;
};
//==============================================================================
// This is an audio source that streams the output of our demo synth.
struct SynthAudioSource : public AudioSource
{
SynthAudioSource (MidiKeyboardState& keyState) : keyboardState (keyState)
{
// Add some voices to our synth, to play the sounds..
for (int i = 4; --i >= 0;)
{
synth.addVoice (new SineWaveVoice()); // These voices will play our custom sine-wave sounds..
synth.addVoice (new SamplerVoice()); // and these ones play the sampled sounds
}
// ..and add a sound for them to play...
setUsingSineWaveSound();
}
void setUsingSineWaveSound()
{
synth.clearSounds();
synth.addSound (new SineWaveSound());
}
void setUsingSampledSound()
{
WavAudioFormat wavFormat;
ScopedPointer<AudioFormatReader> audioReader (wavFormat.createReaderFor (new MemoryInputStream (BinaryData::cello_wav,
BinaryData::cello_wavSize,
false),
true));
BigInteger allNotes;
allNotes.setRange (0, 128, true);
synth.clearSounds();
synth.addSound (new SamplerSound ("demo sound",
*audioReader,
allNotes,
74, // root midi note
0.1, // attack time
0.1, // release time
10.0 // maximum sample length
));
}
void prepareToPlay (int /*samplesPerBlockExpected*/, double sampleRate) override
{
midiCollector.reset (sampleRate);
synth.setCurrentPlaybackSampleRate (sampleRate);
}
void releaseResources() override
{
}
void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override
{
// the synth always adds its output to the audio buffer, so we have to clear it
// first..
bufferToFill.clearActiveBufferRegion();
// fill a midi buffer with incoming messages from the midi input.
MidiBuffer incomingMidi;
midiCollector.removeNextBlockOfMessages (incomingMidi, bufferToFill.numSamples);
// pass these messages to the keyboard state so that it can update the component
// to show on-screen which keys are being pressed on the physical midi keyboard.
// This call will also add midi messages to the buffer which were generated by
// the mouse-clicking on the on-screen keyboard.
keyboardState.processNextMidiBuffer (incomingMidi, 0, bufferToFill.numSamples, true);
// and now get the synth to process the midi events and generate its output.
synth.renderNextBlock (*bufferToFill.buffer, incomingMidi, 0, bufferToFill.numSamples);
}
//==============================================================================
// this collects real-time midi messages from the midi input device, and
// turns them into blocks that we can process in our audio callback
MidiMessageCollector midiCollector;
// this represents the state of which keys on our on-screen keyboard are held
// down. When the mouse is clicked on the keyboard component, this object also
// generates midi messages for this, which we can pass on to our synth.
MidiKeyboardState& keyboardState;
// the synth itself!
Synthesiser synth;
};
//==============================================================================
class AudioSynthesiserDemo : public Component,
private Button::Listener
{
public:
AudioSynthesiserDemo()
: deviceManager (MainAppWindow::getSharedAudioDeviceManager()),
synthAudioSource (keyboardState),
keyboardComponent (keyboardState, MidiKeyboardComponent::horizontalKeyboard)
{
addAndMakeVisible (keyboardComponent);
addAndMakeVisible (sineButton);
sineButton.setButtonText ("Use sine wave");
sineButton.setRadioGroupId (321);
sineButton.addListener (this);
sineButton.setToggleState (true, dontSendNotification);
addAndMakeVisible (sampledButton);
sampledButton.setButtonText ("Use sampled sound");
sampledButton.setRadioGroupId (321);
sampledButton.addListener (this);
addAndMakeVisible (liveAudioDisplayComp);
deviceManager.addAudioCallback (&liveAudioDisplayComp);
audioSourcePlayer.setSource (&synthAudioSource);
deviceManager.addAudioCallback (&audioSourcePlayer);
deviceManager.addMidiInputCallback (String(), &(synthAudioSource.midiCollector));
setOpaque (true);
setSize (640, 480);
}
~AudioSynthesiserDemo()
{
audioSourcePlayer.setSource (nullptr);
deviceManager.removeMidiInputCallback (String(), &(synthAudioSource.midiCollector));
deviceManager.removeAudioCallback (&audioSourcePlayer);
deviceManager.removeAudioCallback (&liveAudioDisplayComp);
}
//==============================================================================
void paint (Graphics& g) override
{
g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground));
}
void resized() override
{
keyboardComponent.setBounds (8, 96, getWidth() - 16, 64);
sineButton.setBounds (16, 176, 150, 24);
sampledButton.setBounds (16, 200, 150, 24);
liveAudioDisplayComp.setBounds (8, 8, getWidth() - 16, 64);
}
private:
AudioDeviceManager& deviceManager;
MidiKeyboardState keyboardState;
AudioSourcePlayer audioSourcePlayer;
SynthAudioSource synthAudioSource;
MidiKeyboardComponent keyboardComponent;
ToggleButton sineButton;
ToggleButton sampledButton;
LiveScrollingAudioDisplay liveAudioDisplayComp;
//==============================================================================
void buttonClicked (Button* buttonThatWasClicked) override
{
if (buttonThatWasClicked == &sineButton)
synthAudioSource.setUsingSineWaveSound();
else if (buttonThatWasClicked == &sampledButton)
synthAudioSource.setUsingSampledSound();
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioSynthesiserDemo)
};
// This static object will register this demo type in a global list of demos..
static JuceDemoType<AudioSynthesiserDemo> demo ("31 Audio: Synthesisers");