Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 38 additions & 14 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,35 @@ get_filename_component(PROJECT ${FILENAME} NAME_WE) #automatically sets project
message(STATUS "Project name is ${PROJECT}")
project (${PROJECT})

find_library(MAPPER_LIB mapper)
if(NOT MAPPER_LIB)
message(FATAL_ERROR "libmapper library not found")
endif()

find_package(Boost 1.73.0 REQUIRED)
if(NOT Boost_FOUND)
message(FATAL_ERROR "boost library not found")
endif()
include_directories(Boost_INCLUDE_DIRS)
if (WIN32)
# EDIT LIBMAPPER_DIR PATH BELOW BEFORE COMPILING FOR WINDOWS
set(LIBMAPPER_DIR "C:/Users/brady/Documents/Github/libmapper")
set(LIBMAPPER_BUILD_DIR "${LIBMAPPER_DIR}/build/Release")
set(LIBLO_DIR "${LIBMAPPER_DIR}/build/liblo/liblo-master")
set(LIBLO_BUILD_DIR "${LIBLO_DIR}/cmake/build/Release")
set(LIBLO_INCLUDES "${LIBLO_DIR}/cmake/build;${LIBLO_DIR}")

set(Liblo_LIB ${LIBLO_BUILD_DIR}/liblo.lib)
set(Libmapper_LIB ${LIBMAPPER_BUILD_DIR}/libmapper.lib)
mark_as_advanced(Liblo_LIB)
mark_as_advanced(Libmapper_LIB)
add_definitions(
-D_WINSOCK_DEPRECATED_NO_WARNINGS
-DHAVE_WINSOCK2_H
-DNODEFAULTLIB
)
include_directories(
"${LIBLO_INCLUDES}"
"${LIBMAPPER_DIR}/include"
)
set(MAPPER_LIB ${Liblo_LIB} ${Libmapper_LIB})
else()
find_library(MAPPER_LIB mapper)
if(NOT MAPPER_LIB)
message(FATAL_ERROR "libmapper library not found")
endif(NOT MAPPER_LIB)
include_directories(/usr/local/include)
endif(WIN32)

if (APPLE)
set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}")
Expand All @@ -33,8 +52,6 @@ include_directories(${SC_PATH}/include/plugin_interface)
include_directories(${SC_PATH}/include/common)
include_directories(${SC_PATH}/common)

include_directories(/usr/local/include)

if (CMAKE_SYSTEM_NAME MATCHES "Linux|.*BSD|DragonFly")
set(INSTALL_DESTINATION "lib/SuperCollider/plugins")
if (QUARKS)
Expand Down Expand Up @@ -136,18 +153,25 @@ endif()
add_library(${PROJECT} MODULE ${FILENAME})
target_link_libraries(${PROJECT} ${MAPPER_LIB} ${Boost_libraries})

if(WIN32)
set_target_properties(${PROJECT} PROPERTIES LINK_FLAGS "/NODEFAULTLIB:MSVCRTD")
set_target_properties(${PROJECT} PROPERTIES LINK_FLAGS "/INCREMENTAL:NO")
endif(WIN32)

if(SUPERNOVA)
add_library(${PROJECT}_supernova MODULE ${FILENAME})
target_link_libraries(${PROJECT}_supernova ${MAPPER_LIB})
set_property(TARGET ${PROJECT}_supernova
PROPERTY COMPILE_DEFINITIONS SUPERNOVA)
if(NOT WIN32)
install(TARGETS ${PROJECT}_supernova
DESTINATION ${INSTALL_DESTINATION}/${PLUGIN_DIR})
endif(NOT WIN32)
endif()


if(NOT WIN32)
install(TARGETS ${PROJECT}
DESTINATION ${INSTALL_DESTINATION}/${PLUGIN_DIR})

install(DIRECTORY "sc/" DESTINATION "${INSTALL_DESTINATION_DISTRO}"
PATTERN "*")
endif(NOT WIN32)
139 changes: 90 additions & 49 deletions Mapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
#include <mapper/mapper.h>

#include <atomic>
#include <boost/lockfree/queue.hpp>
#include <functional>
#include <queue>
#include <thread>
Expand All @@ -34,41 +33,41 @@ static mpr_dev dev;
static std::atomic<bool> isReady;
static std::thread* libmapperThreadHandle;

typedef std::function<void()> Task;
static boost::lockfree::queue<Task*> taskQueue(128);

void libmapperThread() {
// TODO(mb): Add option for specifying device name with Mapper.enable(name)
dev = mpr_dev_new("SuperCollider", 0);
void libmapperThread(char* deviceName) {
dev = mpr_dev_new(deviceName, 0);
while (!mpr_dev_get_is_ready(dev)) {
mpr_dev_poll(dev, 10);
}
isReady = true;
Print("Mapper: libmapper ready!\n");
while (dev) {
mpr_dev_poll(dev, 1);
// Execute tasks
Task* f;
if (taskQueue.pop(f)) {
(*f)();
delete f;
}
}
}

struct MapperUnit : public Unit {
struct MapperSignalUnit : public Unit {
int signalNameSize;
char* signalName;
mpr_sig sig;
mpr_sig sig = nullptr;
float sigMin = 0;
float sigMax = 1;
float val = 0;
};

struct MapIn : public MapperUnit {};
struct MapOut : public MapperUnit {};
struct MapperEnabler : public Unit {};
struct MapperDeviceUnit : public Unit {
int deviceNameSize;
char* deviceName;
};

struct MapperIsReadyUnit : public Unit {
bool shouldSendNeg = true; // Send -1 the first frame to trigger free when ready
};

struct MapIn : public MapperSignalUnit {};
struct MapOut : public MapperSignalUnit {};
struct MapperEnabler : public MapperDeviceUnit {};
struct MapperDisabler : public Unit {};
struct MapperIsReady : public MapperIsReadyUnit {};

// Empty DSP function
static void Unit_next_nop(Unit* unit, int inNumSamples) {}
Expand All @@ -84,36 +83,24 @@ static void MapOut_next(MapOut* unit, int inNumSamples);
static void MapperEnabler_Ctor(MapperEnabler* unit);
static void MapperDisabler_Ctor(MapperDisabler* unit);

static void MapIn_signalUpdate(mpr_sig sig, mpr_sig_evt evt, mpr_id inst,
int length, mpr_type type, const void* value,
mpr_time time) {
MapIn* m = (MapIn*)mpr_obj_get_prop_as_ptr(sig, MPR_PROP_DATA, 0);
if (m) {
m->val = *reinterpret_cast<const float*>(value);
}
}
static void MapperIsReady_Ctor(MapperIsReady* unit);
static void MapperIsReady_Dtor(MapperIsReady* unit);
static void MapperIsReady_next(MapperIsReady* unit, int inNumSamples);

static void MapperUnit_bindToSignal(MapperUnit* unit, mpr_dir direction) {
static void MapperSignalUnit_bindToSignal(MapperSignalUnit* unit, mpr_dir direction) {
// Search for existing output signal with same name
mpr_list sigs = mpr_dev_get_sigs(dev, direction);
sigs = mpr_list_filter(sigs, MPR_PROP_NAME, 0, 1, MPR_STR, unit->signalName,
MPR_OP_EQ);

mpr_sig_handler* handler = direction == MPR_DIR_IN ? MapIn_signalUpdate : 0;
int flags = direction == MPR_DIR_IN ? MPR_SIG_UPDATE : 0;

if (sigs) {
// Signal exists, bind unit to signal
unit->sig = *sigs;

// Update pointer for signal update callback
mpr_obj_set_prop(unit->sig, MPR_PROP_DATA, 0, 1, MPR_PTR, unit, 0);
mpr_sig_set_cb(unit->sig, handler, flags);

// Update signal metadata if signal range has changed
// mpr_obj_set_prop(unit->sig, MPR_PROP_MIN, 0, 1, MPR_FLT, &unit->sigMin,
// 1); mpr_obj_set_prop(unit->sig, MPR_PROP_MAX, 0, 1, MPR_FLT,
// &unit->sigMax, 1);
mpr_obj_set_prop(unit->sig, MPR_PROP_MIN, 0, 1, MPR_FLT, &unit->sigMin, 1);
mpr_obj_set_prop(unit->sig, MPR_PROP_MAX, 0, 1, MPR_FLT, &unit->sigMax, 1);
mpr_obj_push(unit->sig);

// Update maps containing signal
// mpr_list maps = mpr_sig_get_maps(unit->sig, MPR_DIR_ANY);
Expand All @@ -135,10 +122,9 @@ static void MapperUnit_bindToSignal(MapperUnit* unit, mpr_dir direction) {
// }
} else {
// Signal doesn't exist, create new
Print("Creating signal '%s'\n", unit->signalName);
Print("Mapper: Creating signal '%s'\n", unit->signalName);
unit->sig = mpr_sig_new(dev, direction, unit->signalName, 1, MPR_FLT, 0,
&unit->sigMin, &unit->sigMax, 0, handler, flags);
mpr_obj_set_prop(unit->sig, MPR_PROP_DATA, 0, 1, MPR_PTR, unit, 0);
&unit->sigMin, &unit->sigMax, 0, 0, 0);
}
}

Expand All @@ -151,6 +137,7 @@ void MapIn_Ctor(MapIn* unit) {
unit->sigMin = IN0(0);
unit->sigMax = IN0(1);

// Set signal name
unit->signalNameSize = IN0(2);
const int signalNameAllocSize = (unit->signalNameSize + 1) * sizeof(char);

Expand All @@ -167,9 +154,9 @@ void MapIn_Ctor(MapIn* unit) {
}
unit->signalName[unit->signalNameSize] = 0;

// Bind to signal
if (dev) {
taskQueue.push(
new Task([unit]() { MapperUnit_bindToSignal(unit, MPR_DIR_IN); }));
MapperSignalUnit_bindToSignal(unit, MPR_DIR_IN);
} else {
Print("MapIn: libmapper not enabled\n");
}
Expand All @@ -181,7 +168,23 @@ void MapIn_Dtor(MapIn* unit) { RTFree(unit->mWorld, unit->signalName); }

void MapIn_next(MapIn* unit, int inNumSamples) {
float* out = OUT(0);
*out = unit->val;

// Signal is not created yet
if (!unit->sig) {
*out = 0.f;
}
// Get signal value pointer
const float* val =
static_cast<const float*>(mpr_sig_get_value(unit->sig, 0, 0));

// Signal doesn't have a value yet
if (!val) {
*out = 0.f;
return;
}

// Set out value to signal value
*out = *val;
}

// MapOut
Expand All @@ -190,6 +193,7 @@ void MapOut_Ctor(MapOut* unit) {
unit->sigMin = IN0(1);
unit->sigMax = IN0(2);

// Set signal name
unit->signalNameSize = IN0(3);
const int signalNameAllocSize = (unit->signalNameSize + 1) * sizeof(char);

Expand All @@ -208,8 +212,7 @@ void MapOut_Ctor(MapOut* unit) {
unit->signalName[unit->signalNameSize] = 0;

if (isReady) {
taskQueue.push(
new Task([unit]() { MapperUnit_bindToSignal(unit, MPR_DIR_OUT); }));
MapperSignalUnit_bindToSignal(unit, MPR_DIR_OUT);
} else {
Print("MapOut: libmapper not enabled\n");
SETCALC(Unit_next_nop);
Expand All @@ -222,17 +225,37 @@ void MapOut_Ctor(MapOut* unit) {
void MapOut_Dtor(MapOut* unit) { RTFree(unit->mWorld, unit->signalName); }

void MapOut_next(MapOut* unit, int inNumSamples) {
// Signal is not ready yet
if (!unit->sig) {
return;
}

// Set output signal value
float val = IN0(0);
taskQueue.push(
new Task([=]() { mpr_sig_set_value(unit->sig, 0, 1, MPR_FLT, &val); }));
mpr_sig_set_value(unit->sig, 0, 1, MPR_FLT, &val);
}

// MapperEnabler

void MapperEnabler_Ctor(MapperEnabler* unit) {
if (!dev) {
// dev = mpr_dev_new("SuperCollider", 0);
libmapperThreadHandle = new std::thread(libmapperThread);
// Set device name
unit->deviceNameSize = IN0(0);
const int deviceNameAllocSize = (unit->deviceNameSize + 1) * sizeof(char);

void* chunk = RTAlloc(unit->mWorld, deviceNameAllocSize);
if (!chunk) {
Print("MapOut: RT memory allocation failed\n");
SETCALC(Unit_next_nop);
return;
}

unit->deviceName = reinterpret_cast<char*>(chunk);
for (int i = 0; i < unit->deviceNameSize; i++) {
unit->deviceName[i] = static_cast<char>(IN0(1 + i));
}
unit->deviceName[unit->deviceNameSize] = 0;
libmapperThreadHandle = new std::thread(libmapperThread, unit->deviceName);
} else {
Print("Mapper: libmapper already enabled.\n");
}
Expand All @@ -250,10 +273,28 @@ void MapperDisabler_Ctor(MapperDisabler* unit) {
SETCALC(Unit_next_nop);
}

// MapperIsReady

void MapperIsReady_Ctor(MapperIsReady* unit) {
SETCALC(MapperIsReady_next);
MapperIsReady_next(unit, 1);
}
void MapperIsReady_Dtor(MapperIsReady* unit) {}
void MapperIsReady_next(MapperIsReady* unit, int inNumSamples) {
float* out = OUT(0);
if (unit->shouldSendNeg) {
*out = -1.0f;
unit->shouldSendNeg = false; // Sent -1 once, send regular value now
} else {
*out = (isReady) ? 1.0f : -1.0f;
}
}

PluginLoad(MapperUGens) {
ft = inTable;
DefineDtorUnit(MapIn);
DefineDtorUnit(MapOut);
DefineSimpleUnit(MapperEnabler);
DefineSimpleUnit(MapperDisabler);
DefineDtorUnit(MapperIsReady);
}
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,40 @@ Please follow the [libmapper](https://github.com/libmapper/libmapper) documentat

## Compilation from source

### GNU/Linux or macOS

```
git clone --recursive https://github.com/IDMIL/MapperUGen.git
cd MapperUGen
mkdir -p build
cmake -B ./build -DSUPERNOVA=ON
cmake --build build --target install
```

### Windows

Note: when cloning supercollider below, if the cloning hangs just execute `git config --global url."https://".insteadOf git://` to fix the issue. It's a known issue with older versions of the supercollider SDK.

```
git clone https://github.com/libmapper/MapperUGen.git
cd MapperUGen
git config --global url."https://".insteadOf git://
git submodule update --init --recursive
```

Now edit the CMakeLists.txt LIBMAPPER_DIR and LIBLO_DIR paths to match with your local libmapper paths. For libmapper installation help, consult the instructions [here](https://github.com/libmapper/libmapper/blob/main/doc/how_to_compile_and_run.md). Finally:

```
mkdir build
cd build
cmake -DSUPERNOVA=ON ..
cmake --build .
```

#### Windows manual installation after compiling

1. Evaluate `Platform.userExtensionDir` in supercollider and create a "Mapper" folder there
2. Copy everything inside the ./sc folder to the Mapper folder
3. Create a "plugins" folder in the Mapper directory
4. Copy the build outputs from ./build/Debug and your libmapper, liblo and zlib .dlls to the plugins folder
5. Reboot the supercollider interpreter
Loading
Loading