Commit e26cbe97 authored by jhammen's avatar jhammen

initial import

parents
CMakeLists.txt.user
project(handrumr)
add_library(${PROJECT_NAME} SHARED
handrumr.cpp
plugin.cpp
sample.cpp
)
# no 'lib' prefix for handrumr.so
set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "")
# linked libraries
target_link_libraries(${PROJECT_NAME} "jsoncpp")
target_link_libraries(${PROJECT_NAME} "sndfile")
install(TARGETS handrumr DESTINATION /usr/local/lib/lv2/${PROJECT_NAME}.lv2)
install(FILES "handrumr.ttl" "manifest.ttl" DESTINATION /usr/local/lib/lv2/${PROJECT_NAME}.lv2)
\ No newline at end of file
This diff is collapsed.
/*
* This file is part of handrumr.lv2.
*
* handrumr.lv2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* handrumr.lv2 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.
*
* You should have received a copy of the GNU General Public License
* along with handrumr.lv2. If not, see <http://www.gnu.org/licenses/>.
*/
#include <math.h>
#include <string.h>
#include <vector>
#include <stdexcept>
#include "sample.h"
#include "plugin.h"
using std::vector;
using handrumr::Plugin;
// --------------------- initializiation
static LV2_Handle
instantiate(const LV2_Descriptor* descriptor, double rate, const char *path,
const LV2_Feature* const* features)
{
try {
return (LV2_Handle)new Plugin(features);
}
catch(const std::logic_error &e) {
return NULL;
}
}
static void connect_port(LV2_Handle instance, uint32_t port, void* data)
{
((Plugin*)instance)->connectPort(port, data);
}
static void
cleanup(LV2_Handle instance)
{
delete (Plugin*)instance;
}
static void
activate(LV2_Handle instance)
{
((Plugin*)instance)->activated(true);
}
static void
deactivate(LV2_Handle instance)
{
((Plugin*)instance)->activated(false);
}
// ----------------- process
static void run(LV2_Handle instance, uint32_t sample_count)
{
((Plugin*)instance)->run(sample_count);
}
// ----------------- state
static LV2_State_Status save(LV2_Handle instance, LV2_State_Store_Function store,
LV2_State_Handle handle, uint32_t flags, const LV2_Feature* const* features)
{
return ((Plugin*)instance)->save(store, handle, flags, features);
}
static LV2_State_Status restore(LV2_Handle instance, LV2_State_Retrieve_Function retrieve,
LV2_State_Handle handle, uint32_t flags, const LV2_Feature* const* features)
{
return ((Plugin*)instance)->restore(retrieve, handle, flags, features);
}
// ----------------- extensions
static const void* extension_data(const char* uri)
{
static const LV2_State_Interface state = { save, restore };
if (!strcmp(uri, LV2_STATE__interface)) {
return &state;
}
return NULL;
}
// ----------------- plugin descriptor
static const LV2_Descriptor descriptor = {
HANDRUMR_URI,
instantiate,
connect_port,
activate,
run,
deactivate,
cleanup,
extension_data
};
LV2_SYMBOL_EXPORT const LV2_Descriptor* lv2_descriptor(uint32_t index)
{
switch (index) {
case 0:
return &descriptor;
default:
return NULL;
}
}
@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
@prefix doap: <http://usefulinc.com/ns/doap#> .
@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
@prefix patch: <http://lv2plug.in/ns/ext/patch#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix state: <http://lv2plug.in/ns/ext/state#> .
@prefix ui: <http://lv2plug.in/ns/extensions/ui#> .
@prefix urid: <http://lv2plug.in/ns/ext/urid#> .
@prefix work: <http://lv2plug.in/ns/ext/worker#> .
@prefix param: <http://lv2plug.in/ns/ext/parameters#> .
<http://lv2plug.in/plugins/handrumr>
a lv2:Plugin ;
doap:name "handrumr.lv2" ;
doap:license <https://opensource.org/licenses/GPL-3.0> ;
lv2:requiredFeature urid:map ,
work:schedule ;
lv2:optionalFeature lv2:hardRTCapable ;
lv2:extensionData state:interface ;
lv2:port [
a lv2:InputPort ,
atom:AtomPort ;
atom:bufferType atom:Sequence ;
atom:supports <http://lv2plug.in/ns/ext/midi#MidiEvent> ,
patch:Message ;
lv2:designation lv2:control ;
lv2:index 0 ;
lv2:symbol "control" ;
lv2:name "Control"
] , [
a lv2:AudioPort ,
lv2:OutputPort ;
lv2:index 1 ;
lv2:symbol "out" ;
lv2:name "Out"
] ;
state:state [
<http://lv2plug.in/plugins/handrumr#config>
'''
{
"instruments" : []
}
'''
] .
/*
* This file is part of handrumr.lv2.
*
* handrumr.lv2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* handrumr.lv2 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.
*
* You should have received a copy of the GNU General Public License
* along with handrumr.lv2. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef INSTRUMENT_H
#define INSTRUMENT_H
#include "layer.h"
namespace handrumr {
class Instrument {
int mKey;
Layer *mLayers;
int layerCount;
public:
Instrument() {}
int key() { return mKey; }
void key(int key) { mKey = key; }
void layers(Layer *layers, int count) {
mLayers = layers;
layerCount = count;
}
Sample *sample(int velocity) {
int index = 0;
while(mLayers[index].min() < velocity
&& index < layerCount) { index++; }
return mLayers[index-1].sample();
}
};
}
#endif // INSTRUMENT_H
\ No newline at end of file
/*
* This file is part of handrumr.lv2.
*
* handrumr.lv2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* handrumr.lv2 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.
*
* You should have received a copy of the GNU General Public License
* along with handrumr.lv2. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LAYER_H
#define LAYER_H
#include "sample.h"
class Layer
{
int mMin;
Sample *mSamples;
int sampleCount;
float totalWeight;
public:
Layer() : totalWeight(0.0) {}
void min(int val) { mMin = val; }
int min() { return mMin; }
void samples(Sample *samples, int count) {
mSamples = samples;
sampleCount = count;
for(int i = 0; i < sampleCount; i++) {
totalWeight += mSamples[i].weight();
mSamples[i].weight(totalWeight);
}
}
Sample *sample() {
float roll = static_cast <float> (rand())
/ (static_cast <float> (RAND_MAX/totalWeight));
int index = 0;
while(mSamples[index].weight() < roll
&& index < sampleCount - 1) { index++; }
return &mSamples[index];
}
};
#endif // LAYER_H
\ No newline at end of file
@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
<http://lv2plug.in/plugins/handrumr>
a lv2:Plugin ;
lv2:binary <handrumr.so> ;
rdfs:seeAlso <handrumr.ttl> .
/*
* This file is part of handrumr.lv2.
*
* handrumr.lv2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* handrumr.lv2 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.
*
* You should have received a copy of the GNU General Public License
* along with handrumr.lv2. If not, see <http://www.gnu.org/licenses/>.
*/
#include <jsoncpp/json/json.h>
#include "plugin.h"
namespace handrumr {
SamplePlay *Plugin::nextSlot()
{
int index = 0;
while(active[index].active()) {
if(++index == MAX_POLYPHONY) { return NULL; }
}
if(index + 1 > maxActive) {
maxActive = index + 1;
}
return &active[index];
}
Plugin::Plugin(const LV2_Feature* const* features)
: maxActive(0)
{
const char* missing = lv2_features_query(features,
LV2_URID__map, &map, true, NULL);
if (missing) {
std::string mesg("Missing feature ");
mesg.append(missing);
throw std::logic_error(mesg);
}
uris.midi_Event = map->map(map->handle, LV2_MIDI__MidiEvent);
uris.atom_string = map->map(map->handle, LV2_ATOM__String);
uris.hd_config = map->map(map->handle, HANDRUMR__config);
}
void Plugin::connectPort(uint32_t port, void *data)
{
switch (port) {
case MIDI_INPUT_PORT:
controlPort = (const LV2_Atom_Sequence*)data;
break;
case AUDIO_OUTPUT_PORT:
outputPort = (float*)data;
break;
default:
break;
}
}
LV2_State_Status Plugin::restore(LV2_State_Retrieve_Function retrieve, LV2_State_Handle handle,
uint32_t flags, const LV2_Feature* const* features)
{
// Get host features
LV2_State_Map_Path* paths = NULL;
const char* missing = lv2_features_query(features,
LV2_STATE__mapPath, &paths, true, NULL);
if (missing) {
printf("Missing feature <%s>\n", missing);
return LV2_STATE_ERR_NO_FEATURE;
}
size_t size;
uint32_t type;
uint32_t valflags;
const void* value = retrieve(handle, uris.hd_config, &size, &type, &valflags);
if (!value) {
printf("Missing config\n");
return LV2_STATE_ERR_NO_PROPERTY;
} else if (type != uris.atom_string) {
printf("Non-string config\n");
return LV2_STATE_ERR_BAD_TYPE;
}
Json::Value root;
Json::Reader reader;
if ( !reader.parse( (char*)value, root )) {
printf("Failed to parse config\n");
return LV2_STATE_ERR_BAD_TYPE;
}
Json::Value jsonInstrs = root["instruments"];
string base = root["folder"].asString();
instruments = new handrumr::Instrument[jsonInstrs.size()];
instrumentCount = jsonInstrs.size();
for(int i = 0; i < instrumentCount; i++) {
instruments[i].key(jsonInstrs[i]["key"].asInt());
// layers
Json::Value jsonLayers = jsonInstrs[i]["layers"];
int layerCount = jsonLayers.size();
Layer *layers = new Layer[layerCount];
for(int l = 0; l < layerCount; l++) {
layers[l].min(jsonLayers[l]["min"].asInt());
// samples
Json::Value jsonSamples = jsonLayers[l]["samples"];
int sampleCount = jsonSamples.size();
Sample *samples = new Sample[sampleCount];
for(int j = 0; j < sampleCount; j++) {
if(jsonSamples[j].isMember("weight")) {
samples[j].weight(jsonSamples[j]["weight"].asFloat());
}
samples[j].load(base + '/' + jsonSamples[j]["file"].asString());
}
layers[l].samples(samples, sampleCount);
}
instruments[i].layers(layers, layerCount);
}
return LV2_STATE_SUCCESS;
}
LV2_State_Status Plugin::save(LV2_State_Store_Function store, LV2_State_Handle handle,
uint32_t flags, const LV2_Feature* const* features)
{
// TODO: implement
return LV2_STATE_SUCCESS;
}
void Plugin::run(uint32_t sampleCount)
{
// loop over incoming events
LV2_ATOM_SEQUENCE_FOREACH(controlPort, ev) {
if (ev->body.type == uris.midi_Event) {
const uint8_t* const msg = (const uint8_t*)(ev + 1);
switch (lv2_midi_message_type(msg)) {
case LV2_MIDI_MSG_NOTE_ON:
{
uint8_t note = msg[1];
for(int i = 0; i < instrumentCount; i++) {
if(instruments[i].key() == note) {
SamplePlay *slot = nextSlot();
if(slot) {
slot->sample(instruments[i].sample(msg[2]), ev->time.frames);
}
}
}
break;
}
default:
break;
}
}
}
// zero out buffer
memset(outputPort, 0, sampleCount * sizeof(float));
// loop over active samples
for(int counter = 0; counter < maxActive; counter++) {
SamplePlay &play = active[counter];
uint32_t i = 0;
while(play.active() && i < sampleCount) {
outputPort[i++] += play.next(i);
}
}
// shrink maxActive if possible
while(maxActive > 0 && !active[maxActive - 1].active()) {
maxActive--;
}
}
}
\ No newline at end of file
/*
* This file is part of handrumr.lv2.
*
* handrumr.lv2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* handrumr.lv2 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.
*
* You should have received a copy of the GNU General Public License
* along with handrumr.lv2. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef PLUGIN_H
#define PLUGIN_H
#include <stdarg.h>
#include <stdlib.h>
#include <sndfile.h>
#include "lv2/lv2plug.in/ns/lv2core/lv2.h"
#include "lv2/lv2plug.in/ns/lv2core/lv2_util.h"
#include "lv2/lv2plug.in/ns/ext/atom/util.h"
#include "lv2/lv2plug.in/ns/ext/atom/forge.h"
#include "lv2/lv2plug.in/ns/ext/midi/midi.h"
#include "lv2/lv2plug.in/ns/ext/state/state.h"
#include "lv2/lv2plug.in/ns/ext/urid/urid.h"
#include "lv2/lv2plug.in/ns/ext/parameters/parameters.h"
#include "instrument.h"
#define HANDRUMR_URI "http://lv2plug.in/plugins/handrumr"
#define HANDRUMR__config HANDRUMR_URI "#config"
#define MAX_POLYPHONY 1024
namespace handrumr {
enum {
MIDI_INPUT_PORT = 0,
AUDIO_OUTPUT_PORT = 1
};
class SamplePlay
{
Sample *mSample;
int32_t index;
uint32_t offset;
public:
SamplePlay() : index(-1) { }
bool active() { return index >= 0; }
void sample(Sample *s, uint32_t off) {
mSample = s;
offset = off;
index = 0;
}
float next(uint32_t i) {
if(i < offset) { return 0; }
float &ret = mSample->data()[index++];
if(index == mSample->size()) {
index = -1;
}
offset = 0;
return ret;
}
};
struct PluginURIs {
LV2_URID midi_Event;
LV2_URID atom_string;
LV2_URID hd_config;
};
class Plugin
{
bool mActivated;
const LV2_Atom_Sequence* controlPort;
float *outputPort;
LV2_URID_Map *map;
PluginURIs uris;
// runtime
SamplePlay active[MAX_POLYPHONY];
int maxActive;
SamplePlay *nextSlot();
// config
Instrument *instruments;
int instrumentCount;
public:
Plugin(const LV2_Feature* const*);
void activated(bool state) { mActivated = state; }
void connectPort(uint32_t port, void *data);
LV2_State_Status restore(LV2_State_Retrieve_Function, LV2_State_Handle,
uint32_t, const LV2_Feature* const*);
LV2_State_Status save(LV2_State_Store_Function, LV2_State_Handle,
uint32_t, const LV2_Feature* const*);
void run(uint32_t sampleCount);
};
}
#endif // PLUGIN_H
\ No newline at end of file
/*
* This file is part of handrumr.lv2.
*
* handrumr.lv2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* handrumr.lv2 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.
*
* You should have received a copy of the GNU General Public License
* along with handrumr.lv2. If not, see <http://www.gnu.org/licenses/>.
*/
#include "sample.h"
#include <stdexcept>
void Sample::load(const string &path)
{
SF_INFO info;
SNDFILE* const sndfile = sf_open(path.c_str(), SFM_READ, &info);
if (!sndfile || !info.frames) {
std::string mesg("failed to load sample ");
mesg.append(path);
throw std::logic_error(mesg);
}
if(info.channels != 1) {
throw std::logic_error("non-mono samples tbd");
}
// Read data
mSize = info.frames;
mData = new float[mSize];
sf_seek(sndfile, 0ul, SEEK_SET);
sf_read_float(sndfile, mData, mSize);
sf_close(sndfile);
}
/*
* This file is part of handrumr.lv2.
*
* handrumr.lv2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* handrumr.lv2 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.
*