|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622 |
- /*
- ==============================================================================
-
- This file is part of the Water library.
- Copyright (c) 2016 ROLI Ltd.
- Copyright (C) 2018 Filipe Coelho <falktx@falktx.com>
-
- Permission is granted to use this software 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" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD
- TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
- FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT,
- OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
- USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
- TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
- OF THIS SOFTWARE.
-
- ==============================================================================
- */
-
- #ifndef WATER_SYNTHESISER_H_INCLUDED
- #define WATER_SYNTHESISER_H_INCLUDED
-
- #include "../buffers/AudioSampleBuffer.h"
- #include "../containers/OwnedArray.h"
- #include "../containers/ReferenceCountedArray.h"
-
- #include "CarlaJuceUtils.hpp"
- #include "CarlaMutex.hpp"
-
- namespace water {
-
- //==============================================================================
- /**
- Describes one of the sounds that a Synthesiser can play.
-
- A synthesiser can contain one or more sounds, and a sound can choose which
- midi notes and channels can trigger it.
-
- The SynthesiserSound is a passive class that just describes what the sound is -
- the actual audio rendering for a sound is done by a SynthesiserVoice. This allows
- more than one SynthesiserVoice to play the same sound at the same time.
-
- @see Synthesiser, SynthesiserVoice
- */
- class SynthesiserSound : public ReferenceCountedObject
- {
- protected:
- //==============================================================================
- SynthesiserSound();
-
- public:
- /** Destructor. */
- virtual ~SynthesiserSound();
-
- //==============================================================================
- /** Returns true if this sound should be played when a given midi note is pressed.
-
- The Synthesiser will use this information when deciding which sounds to trigger
- for a given note.
- */
- virtual bool appliesToNote (int midiNoteNumber) = 0;
-
- /** Returns true if the sound should be triggered by midi events on a given channel.
-
- The Synthesiser will use this information when deciding which sounds to trigger
- for a given note.
- */
- virtual bool appliesToChannel (int midiChannel) = 0;
-
- /** The class is reference-counted, so this is a handy pointer class for it. */
- typedef ReferenceCountedObjectPtr<SynthesiserSound> Ptr;
-
-
- private:
- //==============================================================================
- CARLA_LEAK_DETECTOR (SynthesiserSound)
- };
-
-
- //==============================================================================
- /**
- Represents a voice that a Synthesiser can use to play a SynthesiserSound.
-
- A voice plays a single sound at a time, and a synthesiser holds an array of
- voices so that it can play polyphonically.
-
- @see Synthesiser, SynthesiserSound
- */
- class SynthesiserVoice
- {
- public:
- //==============================================================================
- /** Creates a voice. */
- SynthesiserVoice();
-
- /** Destructor. */
- virtual ~SynthesiserVoice();
-
- //==============================================================================
- /** Returns the midi note that this voice is currently playing.
- Returns a value less than 0 if no note is playing.
- */
- int getCurrentlyPlayingNote() const noexcept { return currentlyPlayingNote; }
-
- /** Returns the sound that this voice is currently playing.
- Returns nullptr if it's not playing.
- */
- SynthesiserSound::Ptr getCurrentlyPlayingSound() const noexcept { return currentlyPlayingSound; }
-
- /** Must return true if this voice object is capable of playing the given sound.
-
- If there are different classes of sound, and different classes of voice, a voice can
- choose which ones it wants to take on.
-
- A typical implementation of this method may just return true if there's only one type
- of voice and sound, or it might check the type of the sound object passed-in and
- see if it's one that it understands.
- */
- virtual bool canPlaySound (SynthesiserSound*) = 0;
-
- /** Called to start a new note.
- This will be called during the rendering callback, so must be fast and thread-safe.
- */
- virtual void startNote (int midiNoteNumber,
- float velocity,
- SynthesiserSound* sound,
- int currentPitchWheelPosition) = 0;
-
- /** Called to stop a note.
-
- This will be called during the rendering callback, so must be fast and thread-safe.
-
- The velocity indicates how quickly the note was released - 0 is slowly, 1 is quickly.
-
- If allowTailOff is false or the voice doesn't want to tail-off, then it must stop all
- sound immediately, and must call clearCurrentNote() to reset the state of this voice
- and allow the synth to reassign it another sound.
-
- If allowTailOff is true and the voice decides to do a tail-off, then it's allowed to
- begin fading out its sound, and it can stop playing until it's finished. As soon as it
- finishes playing (during the rendering callback), it must make sure that it calls
- clearCurrentNote().
- */
- virtual void stopNote (float velocity, bool allowTailOff) = 0;
-
- /** Returns true if this voice is currently busy playing a sound.
- By default this just checks the getCurrentlyPlayingNote() value, but can
- be overridden for more advanced checking.
- */
- virtual bool isVoiceActive() const;
-
- /** Called to let the voice know that the pitch wheel has been moved.
- This will be called during the rendering callback, so must be fast and thread-safe.
- */
- virtual void pitchWheelMoved (int newPitchWheelValue) = 0;
-
- /** Called to let the voice know that a midi controller has been moved.
- This will be called during the rendering callback, so must be fast and thread-safe.
- */
- virtual void controllerMoved (int controllerNumber, int newControllerValue) = 0;
-
- /** Called to let the voice know that the aftertouch has changed.
- This will be called during the rendering callback, so must be fast and thread-safe.
- */
- virtual void aftertouchChanged (int newAftertouchValue);
-
- /** Called to let the voice know that the channel pressure has changed.
- This will be called during the rendering callback, so must be fast and thread-safe.
- */
- virtual void channelPressureChanged (int newChannelPressureValue);
-
- //==============================================================================
- /** Renders the next block of data for this voice.
-
- The output audio data must be added to the current contents of the buffer provided.
- Only the region of the buffer between startSample and (startSample + numSamples)
- should be altered by this method.
-
- If the voice is currently silent, it should just return without doing anything.
-
- If the sound that the voice is playing finishes during the course of this rendered
- block, it must call clearCurrentNote(), to tell the synthesiser that it has finished.
-
- The size of the blocks that are rendered can change each time it is called, and may
- involve rendering as little as 1 sample at a time. In between rendering callbacks,
- the voice's methods will be called to tell it about note and controller events.
- */
- virtual void renderNextBlock (AudioSampleBuffer& outputBuffer,
- int startSample,
- int numSamples) = 0;
-
- /** Changes the voice's reference sample rate.
-
- The rate is set so that subclasses know the output rate and can set their pitch
- accordingly.
-
- This method is called by the synth, and subclasses can access the current rate with
- the currentSampleRate member.
- */
- virtual void setCurrentPlaybackSampleRate (double newRate);
-
- /** Returns true if the voice is currently playing a sound which is mapped to the given
- midi channel.
-
- If it's not currently playing, this will return false.
- */
- virtual bool isPlayingChannel (int midiChannel) const;
-
- /** Returns the current target sample rate at which rendering is being done.
- Subclasses may need to know this so that they can pitch things correctly.
- */
- double getSampleRate() const noexcept { return currentSampleRate; }
-
- /** Returns true if the key that triggered this voice is still held down.
- Note that the voice may still be playing after the key was released (e.g because the
- sostenuto pedal is down).
- */
- bool isKeyDown() const noexcept { return keyIsDown; }
-
- /** Returns true if the sustain pedal is currently active for this voice. */
- bool isSustainPedalDown() const noexcept { return sustainPedalDown; }
-
- /** Returns true if the sostenuto pedal is currently active for this voice. */
- bool isSostenutoPedalDown() const noexcept { return sostenutoPedalDown; }
-
- /** Returns true if a voice is sounding in its release phase **/
- bool isPlayingButReleased() const noexcept
- {
- return isVoiceActive() && ! (isKeyDown() || isSostenutoPedalDown() || isSustainPedalDown());
- }
-
- /** Returns true if this voice started playing its current note before the other voice did. */
- bool wasStartedBefore (const SynthesiserVoice& other) const noexcept;
-
- protected:
- /** Resets the state of this voice after a sound has finished playing.
-
- The subclass must call this when it finishes playing a note and becomes available
- to play new ones.
-
- It must either call it in the stopNote() method, or if the voice is tailing off,
- then it should call it later during the renderNextBlock method, as soon as it
- finishes its tail-off.
-
- It can also be called at any time during the render callback if the sound happens
- to have finished, e.g. if it's playing a sample and the sample finishes.
- */
- void clearCurrentNote();
-
-
- private:
- //==============================================================================
- friend class Synthesiser;
-
- double currentSampleRate;
- int currentlyPlayingNote, currentPlayingMidiChannel;
- uint32 noteOnTime;
- SynthesiserSound::Ptr currentlyPlayingSound;
- bool keyIsDown, sustainPedalDown, sostenutoPedalDown;
-
- AudioSampleBuffer tempBuffer;
-
- CARLA_LEAK_DETECTOR (SynthesiserVoice)
- };
-
-
- //==============================================================================
- /**
- Base class for a musical device that can play sounds.
-
- To create a synthesiser, you'll need to create a subclass of SynthesiserSound
- to describe each sound available to your synth, and a subclass of SynthesiserVoice
- which can play back one of these sounds.
-
- Then you can use the addVoice() and addSound() methods to give the synthesiser a
- set of sounds, and a set of voices it can use to play them. If you only give it
- one voice it will be monophonic - the more voices it has, the more polyphony it'll
- have available.
-
- Then repeatedly call the renderNextBlock() method to produce the audio. Any midi
- events that go in will be scanned for note on/off messages, and these are used to
- start and stop the voices playing the appropriate sounds.
-
- While it's playing, you can also cause notes to be triggered by calling the noteOn(),
- noteOff() and other controller methods.
-
- Before rendering, be sure to call the setCurrentPlaybackSampleRate() to tell it
- what the target playback rate is. This value is passed on to the voices so that
- they can pitch their output correctly.
- */
- class Synthesiser
- {
- public:
- //==============================================================================
- /** Creates a new synthesiser.
- You'll need to add some sounds and voices before it'll make any sound.
- */
- Synthesiser();
-
- /** Destructor. */
- virtual ~Synthesiser();
-
- //==============================================================================
- /** Deletes all voices. */
- void clearVoices();
-
- /** Returns the number of voices that have been added. */
- size_t getNumVoices() const noexcept { return voices.size(); }
-
- /** Returns one of the voices that have been added. */
- SynthesiserVoice* getVoice (int index) const;
-
- /** Adds a new voice to the synth.
-
- All the voices should be the same class of object and are treated equally.
-
- The object passed in will be managed by the synthesiser, which will delete
- it later on when no longer needed. The caller should not retain a pointer to the
- voice.
- */
- SynthesiserVoice* addVoice (SynthesiserVoice* newVoice);
-
- /** Deletes one of the voices. */
- void removeVoice (int index);
-
- //==============================================================================
- /** Deletes all sounds. */
- void clearSounds();
-
- /** Returns the number of sounds that have been added to the synth. */
- int getNumSounds() const noexcept { return sounds.size(); }
-
- /** Returns one of the sounds. */
- SynthesiserSound* getSound (int index) const noexcept { return sounds [index]; }
-
- /** Adds a new sound to the synthesiser.
-
- The object passed in is reference counted, so will be deleted when the
- synthesiser and all voices are no longer using it.
- */
- SynthesiserSound* addSound (const SynthesiserSound::Ptr& newSound);
-
- /** Removes and deletes one of the sounds. */
- void removeSound (int index);
-
- //==============================================================================
- /** If set to true, then the synth will try to take over an existing voice if
- it runs out and needs to play another note.
-
- The value of this boolean is passed into findFreeVoice(), so the result will
- depend on the implementation of this method.
- */
- void setNoteStealingEnabled (bool shouldStealNotes);
-
- /** Returns true if note-stealing is enabled.
- @see setNoteStealingEnabled
- */
- bool isNoteStealingEnabled() const noexcept { return shouldStealNotes; }
-
- //==============================================================================
- /** Triggers a note-on event.
-
- The default method here will find all the sounds that want to be triggered by
- this note/channel. For each sound, it'll try to find a free voice, and use the
- voice to start playing the sound.
-
- Subclasses might want to override this if they need a more complex algorithm.
-
- This method will be called automatically according to the midi data passed into
- renderNextBlock(), but may be called explicitly too.
-
- The midiChannel parameter is the channel, between 1 and 16 inclusive.
- */
- virtual void noteOn (int midiChannel,
- int midiNoteNumber,
- float velocity);
-
- /** Triggers a note-off event.
-
- This will turn off any voices that are playing a sound for the given note/channel.
-
- If allowTailOff is true, the voices will be allowed to fade out the notes gracefully
- (if they can do). If this is false, the notes will all be cut off immediately.
-
- This method will be called automatically according to the midi data passed into
- renderNextBlock(), but may be called explicitly too.
-
- The midiChannel parameter is the channel, between 1 and 16 inclusive.
- */
- virtual void noteOff (int midiChannel,
- int midiNoteNumber,
- float velocity,
- bool allowTailOff);
-
- /** Turns off all notes.
-
- This will turn off any voices that are playing a sound on the given midi channel.
-
- If midiChannel is 0 or less, then all voices will be turned off, regardless of
- which channel they're playing. Otherwise it represents a valid midi channel, from
- 1 to 16 inclusive.
-
- If allowTailOff is true, the voices will be allowed to fade out the notes gracefully
- (if they can do). If this is false, the notes will all be cut off immediately.
-
- This method will be called automatically according to the midi data passed into
- renderNextBlock(), but may be called explicitly too.
- */
- virtual void allNotesOff (int midiChannel,
- bool allowTailOff);
-
- /** Sends a pitch-wheel message to any active voices.
-
- This will send a pitch-wheel message to any voices that are playing sounds on
- the given midi channel.
-
- This method will be called automatically according to the midi data passed into
- renderNextBlock(), but may be called explicitly too.
-
- @param midiChannel the midi channel, from 1 to 16 inclusive
- @param wheelValue the wheel position, from 0 to 0x3fff, as returned by MidiMessage::getPitchWheelValue()
- */
- virtual void handlePitchWheel (int midiChannel,
- int wheelValue);
-
- /** Sends a midi controller message to any active voices.
-
- This will send a midi controller message to any voices that are playing sounds on
- the given midi channel.
-
- This method will be called automatically according to the midi data passed into
- renderNextBlock(), but may be called explicitly too.
-
- @param midiChannel the midi channel, from 1 to 16 inclusive
- @param controllerNumber the midi controller type, as returned by MidiMessage::getControllerNumber()
- @param controllerValue the midi controller value, between 0 and 127, as returned by MidiMessage::getControllerValue()
- */
- virtual void handleController (int midiChannel,
- int controllerNumber,
- int controllerValue);
-
- /** Sends an aftertouch message.
-
- This will send an aftertouch message to any voices that are playing sounds on
- the given midi channel and note number.
-
- This method will be called automatically according to the midi data passed into
- renderNextBlock(), but may be called explicitly too.
-
- @param midiChannel the midi channel, from 1 to 16 inclusive
- @param midiNoteNumber the midi note number, 0 to 127
- @param aftertouchValue the aftertouch value, between 0 and 127,
- as returned by MidiMessage::getAftertouchValue()
- */
- virtual void handleAftertouch (int midiChannel, int midiNoteNumber, int aftertouchValue);
-
- /** Sends a channel pressure message.
-
- This will send a channel pressure message to any voices that are playing sounds on
- the given midi channel.
-
- This method will be called automatically according to the midi data passed into
- renderNextBlock(), but may be called explicitly too.
-
- @param midiChannel the midi channel, from 1 to 16 inclusive
- @param channelPressureValue the pressure value, between 0 and 127, as returned
- by MidiMessage::getChannelPressureValue()
- */
- virtual void handleChannelPressure (int midiChannel, int channelPressureValue);
-
- /** Handles a sustain pedal event. */
- virtual void handleSustainPedal (int midiChannel, bool isDown);
-
- /** Handles a sostenuto pedal event. */
- virtual void handleSostenutoPedal (int midiChannel, bool isDown);
-
- /** Can be overridden to handle soft pedal events. */
- virtual void handleSoftPedal (int midiChannel, bool isDown);
-
- /** Can be overridden to handle an incoming program change message.
- The base class implementation of this has no effect, but you may want to make your
- own synth react to program changes.
- */
- virtual void handleProgramChange (int midiChannel,
- int programNumber);
-
- //==============================================================================
- /** Tells the synthesiser what the sample rate is for the audio it's being used to render.
-
- This value is propagated to the voices so that they can use it to render the correct
- pitches.
- */
- virtual void setCurrentPlaybackSampleRate (double sampleRate);
-
- /** Creates the next block of audio output.
-
- This will process the next numSamples of data from all the voices, and add that output
- to the audio block supplied, starting from the offset specified. Note that the
- data will be added to the current contents of the buffer, so you should clear it
- before calling this method if necessary.
-
- The midi events in the inputMidi buffer are parsed for note and controller events,
- and these are used to trigger the voices. Note that the startSample offset applies
- both to the audio output buffer and the midi input buffer, so any midi events
- with timestamps outside the specified region will be ignored.
- */
- inline void renderNextBlock (AudioSampleBuffer& outputAudio,
- const MidiBuffer& inputMidi,
- int startSample,
- int numSamples)
- { processNextBlock (outputAudio, inputMidi, startSample, numSamples); }
-
- /** Returns the current target sample rate at which rendering is being done.
- Subclasses may need to know this so that they can pitch things correctly.
- */
- double getSampleRate() const noexcept { return sampleRate; }
-
- /** Sets a minimum limit on the size to which audio sub-blocks will be divided when rendering.
-
- When rendering, the audio blocks that are passed into renderNextBlock() will be split up
- into smaller blocks that lie between all the incoming midi messages, and it is these smaller
- sub-blocks that are rendered with multiple calls to renderVoices().
-
- Obviously in a pathological case where there are midi messages on every sample, then
- renderVoices() could be called once per sample and lead to poor performance, so this
- setting allows you to set a lower limit on the block size.
-
- The default setting is 32, which means that midi messages are accurate to about < 1ms
- accuracy, which is probably fine for most purposes, but you may want to increase or
- decrease this value for your synth.
-
- If shouldBeStrict is true, the audio sub-blocks will strictly never be smaller than numSamples.
-
- If shouldBeStrict is false (default), the first audio sub-block in the buffer is allowed
- to be smaller, to make sure that the first MIDI event in a buffer will always be sample-accurate
- (this can sometimes help to avoid quantisation or phasing issues).
- */
- void setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict = false) noexcept;
-
- /** Renders the voices for the given range.
- By default this just calls renderNextBlock() on each voice, but you may need
- to override it to handle custom cases.
- */
- virtual void renderVoices (AudioSampleBuffer& outputAudio,
- int startSample, int numSamples);
-
- /** Can be overridden to do custom handling of incoming midi events. */
- virtual void handleMidiEvent (const MidiMessage&);
-
- protected:
- //==============================================================================
- OwnedArray<SynthesiserVoice> voices;
- ReferenceCountedArray<SynthesiserSound> sounds;
-
- /** The last pitch-wheel values for each midi channel. */
- int lastPitchWheelValues [16];
-
- /** Searches through the voices to find one that's not currently playing, and
- which can play the given sound.
-
- Returns nullptr if all voices are busy and stealing isn't enabled.
-
- To implement a custom note-stealing algorithm, you can either override this
- method, or (preferably) override findVoiceToSteal().
- */
- virtual SynthesiserVoice* findFreeVoice (SynthesiserSound* soundToPlay,
- int midiChannel,
- int midiNoteNumber,
- bool stealIfNoneAvailable) const;
-
- /** Chooses a voice that is most suitable for being re-used.
- The default method will attempt to find the oldest voice that isn't the
- bottom or top note being played. If that's not suitable for your synth,
- you can override this method and do something more cunning instead.
- */
- virtual SynthesiserVoice* findVoiceToSteal (SynthesiserSound* soundToPlay,
- int midiChannel,
- int midiNoteNumber) const;
-
- /** Starts a specified voice playing a particular sound.
- You'll probably never need to call this, it's used internally by noteOn(), but
- may be needed by subclasses for custom behaviours.
- */
- void startVoice (SynthesiserVoice* voice,
- SynthesiserSound* sound,
- int midiChannel,
- int midiNoteNumber,
- float velocity);
-
- /** Stops a given voice.
- You should never need to call this, it's used internally by noteOff, but is protected
- in case it's useful for some custom subclasses. It basically just calls through to
- SynthesiserVoice::stopNote(), and has some assertions to sanity-check a few things.
- */
- void stopVoice (SynthesiserVoice*, float velocity, bool allowTailOff);
-
- private:
- //==============================================================================
- void processNextBlock (AudioSampleBuffer& outputAudio,
- const MidiBuffer& inputMidi,
- int startSample,
- int numSamples);
- //==============================================================================
- double sampleRate;
- uint32 lastNoteOnCounter;
- int minimumSubBlockSize;
- bool subBlockSubdivisionIsStrict;
- bool shouldStealNotes;
- bool sustainPedalsDown[17];
-
- CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Synthesiser)
- };
-
- }
-
- #endif // WATER_SYNTHESISER_H_INCLUDED
|