Skip to content

Commit

Permalink
Virtual tracks (#28)
Browse files Browse the repository at this point in the history
* Pause synth after inactivity

* Optional pause

* Rudimentary virtual tracks

* Update listeners on connection remove

* Enabled pause for VirtualModule tracks

* Get next free track instead of next track

* Fixed some warnings

* Lockable is now mutable

* Added some locks

* Added a method for removing listeners

* Updated doc

* Render VirtualModule with a tint

* Updated docs

* Updated moduleId

* Added volume module

* Remove debug messages

* Updated TOC
  • Loading branch information
kometbomb committed Jul 20, 2018
1 parent 46fd1f5 commit f5d3713
Show file tree
Hide file tree
Showing 14 changed files with 550 additions and 17 deletions.
34 changes: 34 additions & 0 deletions MODULES.md
Expand Up @@ -6,6 +6,7 @@ Modules are loosely divided in three categories: Control, Generators and Modifie
- [Good to know](#good-to-know)
- [Control](#control)
- [FrequencyIn](#frequencyin)
- [Volume](#volume)
- [TriggerNote](#triggernote)
- [Const](#const)
- [Effect](#effect)
Expand Down Expand Up @@ -40,6 +41,7 @@ Modules are loosely divided in three categories: Control, Generators and Modifie
- [Reverb](#reverb)
- [Container](#container)
- [ExtIn/ExtOut](#extinextout)
- [Virtual](#virtual)

## Good to know

Expand All @@ -57,6 +59,12 @@ These modules are used to control other modules.
|--------|-------------|
| 0 | Keydown frequency in kHz |

### Volume

| Output | Description |
|--------|-------------|
| 0 | Current track volume (0..1.0) |

### TriggerNote

Note that this is just the initial keypress. You need to implement key-off functionality yourself (e.g. with the Effect module).
Expand Down Expand Up @@ -483,3 +491,29 @@ host synth. Looking from outside the Container, these show up as normal
module inputs and outputs on the Container.

Use the mouse wheel to select which input or output the modules connect to.

### Virtual

This module can be used to host multiple polyphonic instances of the same layout.
The difference between a Container and a Virtual module is that Virtual
only has left/right/mono audio outputs and the hosted layout needs to use the
AudioOut module for any outputs. ExtIn modules can be used as with Container.

Whenever the AudioOut signal is close to zero for some duration the track
is stopped and queued as the next available track. This means all layouts
should e.g. fade out when TriggerNote is not outputting a signal.

The first input is required and it can be routed from e.g. TriggerNote.
Whenever the first input is triggered a new virtual track is picked from the
available tracks and the ExtIn signals are routed to the new track.

| Input | Description |
|-------|--------------|
| 0 | KeyOn |
| 1.. | ExtIn 0.. |

| Output | Description |
|--------|--------------|
| 0 | Mono out |
| 1 | Left out |
| 2 | Right out |
1 change: 1 addition & 0 deletions module-ids.md
Expand Up @@ -19,3 +19,4 @@ Here's a list of reserved module IDs.
| 37 | eg-hard-reset |
| 39 | divisor-module |
| 40 | midi-controlelr-module |
| 41 | virtual-tracks |
2 changes: 2 additions & 0 deletions src/Listenable.h
Expand Up @@ -12,6 +12,8 @@ know when they need a redraw.
*/

struct Listener;

class Listenable
{
protected:
Expand Down
4 changes: 2 additions & 2 deletions src/Lockable.cpp
Expand Up @@ -13,7 +13,7 @@ Lockable::~Lockable()
}


void Lockable::lock()
void Lockable::lock() const
{
SDL_AtomicLock(&mSpinlock);
if (++mLockCounter > 0)
Expand All @@ -28,7 +28,7 @@ void Lockable::lock()
}


void Lockable::unlock()
void Lockable::unlock() const
{
SDL_AtomicLock(&mSpinlock);
if (mLockCounter-- > 0)
Expand Down
10 changes: 5 additions & 5 deletions src/Lockable.h
Expand Up @@ -4,14 +4,14 @@

class Lockable
{
SDL_mutex *mMutex;
SDL_SpinLock mSpinlock;
int mLockCounter;
mutable SDL_mutex *mMutex;
mutable SDL_SpinLock mSpinlock;
mutable int mLockCounter;

public:
Lockable();
~Lockable();

void lock();
void unlock();
void lock() const;
void unlock() const;
};
1 change: 1 addition & 0 deletions src/MainEditor.cpp
@@ -1,6 +1,7 @@
#include "Debug.h"
#include "MainEditor.h"
#include "IPlayer.h"
#include "Debug.h"
#include "PatternEditor.h"
#include "Oscilloscope.h"
#include "MacroEditor.h"
Expand Down
120 changes: 113 additions & 7 deletions src/ModularSynth.cpp
Expand Up @@ -16,8 +16,9 @@
#define TUNING 440.0
#endif

ModularSynth::ModularSynth(Synth& synth, IPlayer& player)
: mSynth(synth), mPlayer(player), mNumConnections(0), mFrequency(0), mVolume(0), mNoteTrigger(false)
ModularSynth::ModularSynth(Synth& synth, IPlayer& player, bool isPausable, const ModularSynth* parent)
: mSynth(synth), mPlayer(player), mNumConnections(0), mFrequency(0), mVolume(0), mNoteTrigger(false),
mSilenceLength(0), mPaused(true), mIsPausable(isPausable), mParentSynth(parent)
{
strcpy(mName, "");

Expand All @@ -36,14 +37,21 @@ ModularSynth::~ModularSynth()

bool ModularSynth::addModule(int index, int moduleId)
{
lockParent();
ModuleFactory moduleFactory;

if (mModules[index] != NULL)
{
unlockParent();
return false;
}

mModules[index] = moduleFactory.createModule(moduleId, *this);
mModules[index]->setSampleRate(mSampleRate);
mModules[index]->onLoaded();
unlockParent();

mSynthChangeListenable.notify();

return true;
}
Expand Down Expand Up @@ -73,6 +81,8 @@ bool ModularSynth::connectModules(int fromModule, int toModule, int fromOutput,

mNumConnections++;

mSynthChangeListenable.notify();

return true;
}

Expand Down Expand Up @@ -107,18 +117,25 @@ void ModularSynth::removeConnection(int index)
{
if (index < mNumConnections)
{
lockParent();
SynthConnection& connection = mConnections[index];
if (mModules[connection.toModule] != NULL)
mModules[connection.toModule]->setInput(connection.toInput, 0.0f);

mNumConnections--;
memmove(&mConnections[index], &mConnections[index + 1], sizeof(mConnections[index]) * (mNumConnections - index));
unlockParent();
mSynthChangeListenable.notify();
}
}


void ModularSynth::cycle()
{
// Do nothing if the synth has been paused after silence
if (mPaused && mIsPausable)
return;

for (int i = 0 ; i < mNumConnections ; ++i)
{
SynthConnection& connection = mConnections[i];
Expand All @@ -132,12 +149,34 @@ void ModularSynth::cycle()
// After all modules have had their change to see if note was triggered,
// reset the status
mNoteTrigger = false;

if (mIsPausable)
{
// Increase silence counter and check if we are outputting non-silence

for (int i = 0 ; i < 2 ; ++i)
{
if (fabs(mOutput[i]) > silenceThreshold)
{
mSilenceLength = 0;
}
}

if (++mSilenceLength > silenceDurationUntilPause)
{
mPaused = true;
}
}
}


void ModularSynth::triggerNote()
{
mNoteTrigger = true;

// Resume synth and reset silence counter
mPaused = false;
mSilenceLength = 0;
}


Expand All @@ -160,10 +199,14 @@ void ModularSynth::update(int numSamples)

void ModularSynth::render(Sample16 *buffer, int numSamples, int offset)
{
if (!mIsEnabled)
return;

lock();

// No update or output if volume is zero, meaning the channel is muted
if (mVolume > 0.0f)
// No update or output if paused

if (!mPaused)
{
for (int i = offset ; i < numSamples ; ++i)
{
Expand Down Expand Up @@ -222,11 +265,15 @@ void ModularSynth::swapModules(int fromModule, int toModule)
else if (connection.toModule == fromModule)
connection.toModule = toModule;
}

mSynthChangeListenable.notify();
}


void ModularSynth::removeModule(int index)
{
lockParent();

if (mModules[index] != NULL)
{
delete mModules[index];
Expand All @@ -243,6 +290,14 @@ void ModularSynth::removeModule(int index)
else
++i;
}

unlockParent();

mSynthChangeListenable.notify();
}
else
{
unlockParent();
}
}

Expand Down Expand Up @@ -450,6 +505,7 @@ void ModularSynth::setSampleRate(int rate)

void ModularSynth::copy(const ModularSynth& source)
{
lockParent();
strncpy(mName, source.getName(), sizeof(mName));

for (int i = 0 ; i < maxModules ; ++i)
Expand All @@ -460,7 +516,7 @@ void ModularSynth::copy(const ModularSynth& source)
const SynthModule *oldModule = source.getModule(i);
if (oldModule != NULL)
{
if (addModule(i, source.getModule(i)->getSynthId()))
if (addModule(i, oldModule->getSynthId()))
{
SynthModule *newModule = getModule(i);
newModule->copy(*oldModule);
Expand All @@ -475,12 +531,14 @@ void ModularSynth::copy(const ModularSynth& source)
const SynthConnection& connection = source.getConnection(i);
connectModules(connection.fromModule, connection.toModule, connection.fromOutput, connection.toInput);
}

unlockParent();
}


ModularSynth* ModularSynth::createEmpty() const
ModularSynth* ModularSynth::createEmpty(bool isPausable) const
{
return new ModularSynth(mSynth, mPlayer);
return new ModularSynth(mSynth, mPlayer, isPausable, this);
}


Expand Down Expand Up @@ -569,3 +627,51 @@ void ModularSynth::onShow()
module->onShow();
}
}


bool ModularSynth::isPaused() const
{
return mPaused;
}


void ModularSynth::addChangeListener(Listener* listener)
{
mSynthChangeListenable.addListener(listener);
}


void ModularSynth::removeChangeListener(Listener* listener)
{
mSynthChangeListenable.removeListener(listener);
}


float ModularSynth::getOutput(int index) const
{
return mOutput[index];
}


void ModularSynth::lockParent() const
{
if (mParentSynth == NULL)
lock();
else
mParentSynth->lockParent();
}


void ModularSynth::unlockParent() const
{
if (mParentSynth == NULL)
unlock();
else
mParentSynth->unlockParent();
}


float ModularSynth::getVolume() const
{
return mVolume;
}

0 comments on commit f5d3713

Please sign in to comment.