Skip to content

Commit

Permalink
scsynth: adding BelaScopeUGen
Browse files Browse the repository at this point in the history
This is a squash and reformatting of several commits that originally made up #75

BelaScope: create in SC_World, setup in SC_Bela

ServerOptions: -O belaMaxScopeChannels

BelaScope: single instance and valid inputs checks

BelaScopeUGen: single instance guard

BelaScope: add .belaScope to Server, Bus and Function

BelaScope: maxScopeChannels defaults to 0
  • Loading branch information
elgiano authored and giuliomoro committed Oct 22, 2020
1 parent 45a84a4 commit 2fc8ce6
Show file tree
Hide file tree
Showing 11 changed files with 290 additions and 8 deletions.
12 changes: 12 additions & 0 deletions SCClassLibrary/Common/Audio/bela/BELAUGens.sc
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,15 @@ DigitalIO : UGen {
^this.multiNew('control', digitalPin, output, pinMode ).madd(mul,add)
}
}

/* input 1: bus
* input 2: number of channels to scope
*/
BelaScopeUGen : AbstractOut {
*ar { arg busnum, numChannels;
super.performList('new1', 'audio', In.ar(busnum, numChannels));
^0.0; // BelaScopeUGen has no outputs
}
*numFixedArgs { ^0 }
writesToBus { ^false }
}
181 changes: 181 additions & 0 deletions SCClassLibrary/Common/Audio/bela/BelaScope.sc
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
BelaScope {

classvar <serverScopes;
var <server, <bus, <node;

// public interface

*scope { |channelOffset, signals, server|
^this.getInstance(server).scope(channelOffset, signals);
}

scope { |channelOffset, signals|

var ugens = this.class.prInputAsAudioRateUGens(signals);

if(ugens.notNil and: this.prIsValidScopeChannel(channelOffset, signals)) {
Out.ar(this.bus.index + channelOffset, ugens);
};

^signals;
}

*monitorBus { |channelOffset, busindex, numChannels, target|
var server, belaScope;
target = target.asTarget;
server = target.server;
belaScope = this.getInstance(server);
if(belaScope.prIsValidScopeChannel(channelOffset, busindex+(0..numChannels))) {
^Monitor().play(busindex, numChannels, belaScope.bus.index + channelOffset, numChannels, target, addAction:\addAfter);
}
}

maxChannels { ^this.server.options.belaMaxScopeChannels }

// instance creation

*initClass {
serverScopes = IdentityDictionary[];
}

*new { |server|
^this.getInstance(server);
}

*getInstance { |server|
server = server ? Server.default;
serverScopes[server] ?? {
serverScopes[server] = super.newCopyArgs(server).init;
}
^serverScopes[server];
}

init {
if(this.maxChannels <= 0) {
Error(
"BelaScope: can't instantiate on server '%' because its option belaMaxScopeChannels is %"
.format(server, this.maxChannels)
).throw;
};
ServerBoot.add(this, this.server);
ServerTree.add(this, this.server);
if(this.server.serverRunning){
this.doOnServerBoot;
this.doOnServerTree;
}
}

// bus and synth creation

prReserveScopeBus {
// TODO: check if bus is already reserved, or if maxChannels mismatch
bus = Bus.audio(server, this.maxChannels);
}

prStartScope {
if(node.notNil) {
if (node.isRunning) {
warn("BelaScope: can't instantiate a new BelaScopeUGen, because one is already active.");
^this
}
};

if(UGen.buildSynthDef.notNil) {
// When BelaScope.init is called inside a SynthDef function (e.g. by UGen:belaScope),
// this.prStartSynth breaks that SynthDef:build, because it attempts to create an inner SynthDef.
// Fixed by forking.
fork{ this.prStartSynth };
} {
this.prStartSynth;
}

}

prStartSynth {
node = SynthDef(\bela_stethoscope) {
BelaScopeUGen.ar(this.bus, this.maxChannels);
}
.play(this.server, addAction: \addAfter)
.register;
}

doOnServerBoot { this.prReserveScopeBus; ServerBoot.remove(this) }
doOnServerTree { this.prStartScope; ServerTree.remove(this) }

// scope input checks

*prInputAsAudioRateUGens { |signals|
var arUGens = signals.asArray.collect{ |item|
switch(item.rate)
{ \audio }{ item } // pass
{ \control }{ K2A.ar(item) } // convert kr to ar
{ \scalar }{
// convert numbers to ar UGens
if(item.isNumber) { DC.ar(item) } { nil }
}
{ nil }
};

if(arUGens.every(_.isUGen)) {
^arUGens;
} {
warn(
"BelaScope: can't scope this signal, because not all of its elements are UGens.\nSignal: %"
.format(signals)
);
^nil;
}
}

prIsValidScopeChannel { |channelOffset, signals=#[]|
if(channelOffset.isNumber.not) {
warn("BelaScope: channel offset must be a number, but (%) is provided.".format(channelOffset));
^false;
};
if(channelOffset < 0) {
warn("BelaScope: channel offset must be a positive number, but (%) is provided.".format(channelOffset));
^false;
};
if(channelOffset + signals.asArray.size > this.maxChannels){
warn(
"BelaScope: can't scope this signal to scope channel (%), max number of channels (%) exceeded.\nSignal: %"
.format(channelOffset, this.maxChannels, signals)
);
^false;
};
^true;
}
}

+ UGen {
belaScope { |scopeChannel, server|
^BelaScope.scope(scopeChannel, this, server)
}
}

+ Array {
belaScope { |scopeChannel, server|
^BelaScope.scope(scopeChannel, this, server)
}
}

+ Bus {
belaScope { |scopeChannel|
^BelaScope.monitorBus(scopeChannel, index, numChannels);
}
}

+ Function {
belaScope { |scopeChannel, numChannels = 1, target, outbus = 0, fadeTime = 0.02, addAction = \addToHead, args|
var synth = this.play(target, outbus, fadeTime, addAction, args);
var monitor = BelaScope.monitorBus(scopeChannel, outbus, numChannels, target);
^synth.onFree { if(monitor.notNil) { monitor.free } };
}
}

+ Server {
belaScope { |scopeChannel, numChannels, index = 0|
numChannels = numChannels ?? { if (index == 0) { options.numOutputBusChannels } { 2 } };
^Bus(\audio, index, numChannels, this).belaScope(scopeChannel);
}
}
5 changes: 5 additions & 0 deletions SCClassLibrary/Common/Control/Server.sc
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ ServerOptions {
var <>adcLevel;
var <>numMultiplexChannels;
var <>belaPRU;
var <>belaMaxScopeChannels;

*initClass {
defaultValues = IdentityDictionary.newFrom(
Expand Down Expand Up @@ -122,6 +123,7 @@ ServerOptions {
adcLevel: 0,
numMultiplexChannels: 0,
belaPRU: 1,
belaMaxScopeChannels: 0,
)
)
}
Expand Down Expand Up @@ -283,6 +285,9 @@ ServerOptions {
if (belaPRU.notNil, {
o = o ++ " -T " ++ belaPRU;
});
if (belaMaxScopeChannels.notNil, {
o = o ++ " -O " ++ belaMaxScopeChannels;
});
^o
}

Expand Down
4 changes: 3 additions & 1 deletion include/plugin_interface/SC_World.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

#ifdef BELA
# include "Bela.h"
# include "libraries/Scope/Scope.h"
#endif

#include "SC_Types.h"
Expand Down Expand Up @@ -110,7 +111,8 @@ struct World {

#ifdef BELA
BelaContext* mBelaContext;
// uint32 mBelaAnalogChannels;
Scope* mBelaScope;
uint32 mBelaMaxScopeChannels;
uint32 mBelaAnalogInputChannels;
uint32 mBelaAnalogOutputChannels;
uint32 mBelaDigitalChannels;
Expand Down
2 changes: 1 addition & 1 deletion include/server/SC_WorldOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ struct WorldOptions {
int mSharedMemoryID = 0;

#ifdef BELA
// uint32 mBelaAnalogChannels;
uint32 mBelaAnalogInputChannels;
uint32 mBelaAnalogOutputChannels;
uint32 mBelaDigitalChannels;
Expand All @@ -93,6 +92,7 @@ struct WorldOptions {
float mBelaADCLevel;
uint32 mBelaNumMuxChannels;
uint32 mBelaPRU;
uint32 mBelaMaxScopeChannels;
#endif
};

Expand Down
60 changes: 60 additions & 0 deletions server/plugins/BELAUGens.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1331,6 +1331,65 @@ void BelaScope_Dtor(BelaScope *unit)
}
*/

struct BelaScopeUGen : public Unit {
static unsigned int instanceCount;

Scope* belaScope;
float* frameData;
unsigned int noScopeChannels;
unsigned int maxScopeChannels;
};

unsigned int BelaScopeUGen::instanceCount = 0;

void BelaScopeUGen_next(BelaScopeUGen* unit, unsigned int numSamples) {
unsigned int numChannels = unit->noScopeChannels;
unsigned int maxChannels = unit->maxScopeChannels;
float* frameData = unit->frameData;
float* inputPointers[maxChannels];
for (unsigned int ch = 0; ch < numChannels; ++ch)
inputPointers[ch] = ZIN(ch);

LOOP1(numSamples, for (unsigned int ch = 0; ch < numChannels; ++ch) frameData[ch] = ZXP(inputPointers[ch]);
for (unsigned int ch = numChannels; ch < maxChannels; ++ch) frameData[ch] = 0.0;
unit->belaScope->log(frameData);)
}

void BelaScopeUGen_noop(unsigned int numFrames) { /* no-op */
}

void BelaScopeUGen_Ctor(BelaScopeUGen* unit) {
uint32 numInputs = unit->mNumInputs;
uint32 maxScopeChannels = unit->mWorld->mBelaMaxScopeChannels;
if (numInputs > maxScopeChannels) {
rt_fprintf(stderr,
"BelaScopeUGen warning: can't initialise scope %i channels, maxBelaScopeChannels is set to %i\n",
numInputs, maxScopeChannels);
}
BelaScopeUGen::instanceCount++;
if (BelaScopeUGen::instanceCount > 1) {
rt_fprintf(
stderr,
"BelaScopeUGen warning: creating a new instance when one is already active. This one will do nothing.\n");
SETCALC(BelaScopeUGen_noop);
return;
};
unit->noScopeChannels = sc_min(numInputs, maxScopeChannels);
unit->maxScopeChannels = maxScopeChannels;
unit->frameData = (float*)RTAlloc(unit->mWorld, sizeof(float) * unit->noScopeChannels);
unit->belaScope = unit->mWorld->mBelaScope;
// initiate first sample
BelaScopeUGen_next(unit, 1);
// set calculation method
SETCALC(BelaScopeUGen_next);
}

void BelaScopeUGen_Dtor(BelaScopeUGen* unit) {
if (unit->frameData)
RTFree(unit->mWorld, unit->frameData);
BelaScopeUGen::instanceCount--;
}

//////////////////////////////////////////////////////////////////////////////////////////////////

// extern "C"
Expand Down Expand Up @@ -1380,6 +1439,7 @@ PluginLoad(BELA) {
DefineSimpleUnit(DigitalIn);
DefineSimpleUnit(DigitalOut);
DefineSimpleUnit(DigitalIO);
DefineDtorUnit(BelaScopeUGen);
}


Expand Down
1 change: 1 addition & 0 deletions server/plugins/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ if (BELA_FOUND)
add_definitions("-DBELA" ${XENOMAI_DEFINITIONS} ${BELA_DEFINITIONS})
include_directories(${XENOMAI_INCLUDE_DIRS})
include_directories(${BELA_INCLUDE_DIRS})
include_directories(${BELA_INCLUDE_DIRS}/../)

# set(CMAKE_EXECUTABLE_RUNTIME_C_FLAG "-Wl,-wrap,clock_gettime,-rpath,")
# set(CMAKE_EXECUTABLE_RUNTIME_CXX_FLAG "-Wl,-wrap,clock_gettime,-rpath,")
Expand Down
4 changes: 2 additions & 2 deletions server/scsynth/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ elseif(AUDIOAPI STREQUAL portaudio)
target_link_libraries(libscsynth ${PORTAUDIO_LIBRARIES})
endif()
elseif(AUDIOAPI STREQUAL bela)
target_link_libraries(libscsynth ${XENOMAI_LIBRARIES} ${BELA_LIBRARIES})
target_link_libraries(libscsynth ${XENOMAI_LIBRARIES} ${BELA_LIBRARIES} belaextra)
elseif(AUDIOAPI STREQUAL coreaudio)
target_link_libraries(libscsynth "-framework CoreAudio")
endif()
Expand Down Expand Up @@ -265,7 +265,7 @@ add_executable(scsynth
target_link_libraries(scsynth libscsynth)

if(AUDIOAPI STREQUAL bela)
target_link_libraries(scsynth ${XENOMAI_LIBRARIES} ${BELA_LIBRARIES})
target_link_libraries(scsynth ${XENOMAI_LIBRARIES} ${BELA_LIBRARIES} belaextra)
endif()

if (PTHREADS_FOUND)
Expand Down
Loading

0 comments on commit 2fc8ce6

Please sign in to comment.