commit a0d54946b1aaf18caab5578b23264b21beff9157 Author: Peter Nelson Date: Wed Jan 13 10:35:51 2010 +0000 Initial checkin diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..84d3f13 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,66 @@ +cmake_minimum_required(VERSION 2.0) + +PROJECT(psyn) + +INCLUDE( ${CMAKE_ROOT}/Modules/FindPkgConfig.cmake ) +INCLUDE( ${CMAKE_ROOT}/Modules/CheckIncludeFile.cmake ) + +pkg_check_modules(LV2CORE lv2core) +#pkg_check_modules(JACK jack>=0.118) +#pkg_check_modules(GTKMM gtkmm-2.4>=2.4) +#pkg_check_modules(CAIROMM cairomm-1.0>=1.0) +#pkg_check_modules(DBUS dbus-glib-1) +#pkg_check_modules(PCRE libpcrecpp) + +#ADD_CUSTOM_COMMAND( +# OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/jsweeper.ui +# COMMAND gtk-builder-convert ${CMAKE_CURRENT_SOURCE_DIR}/src/jsweeper.glade ${CMAKE_CURRENT_BINARY_DIR}/jsweeper.ui +# DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/jsweeper.glade +#) + +SET(SOURCES + engine.c + engine.h + env.c + env.h + osc.c + osc.h + psyn.c + psyn.h + voice.c + voice.h +) + +LINK_DIRECTORIES( + ${LV2CORE_LIBRARY_DIRS} +# ${JACK_LIBRARY_DIRS} +# ${GTKMM_LIBRARY_DIRS} +# ${CAIROMM_LIBRARY_DIRS} +# ${DBUS_LIBRARY_DIRS} +# ${PCRE_LIBRARY_DIRS} +) + +INCLUDE_DIRECTORIES( + ${LV2CORE_INCLUDE_DIRS} +# ${JACK_INCLUDE_DIRS} +# ${GTKMM_INCLUDE_DIRS} +# ${CAIROMM_INCLUDE_DIRS} +# ${DBUS_INCLUDE_DIRS} +# ${PCRE_INCLUDE_DIRS} +) + +ADD_DEFINITIONS(-g -O3 -Wall -Wextra -pedantic -std=c99 -D_GNU_SOURCE -fPIC -DPIC) +ADD_LIBRARY(psyn SHARED ${SOURCES}) + +TARGET_LINK_LIBRARIES(psyn + -lm + ${LV2CORE_LIBRARIES} +# ${JACK_LIBRARIES} +# ${GTKMM_LIBRARIES} +# ${CAIROMM_LIBRARIES} +# ${DBUS_LIBRARIES} +# ${PCRE_LIBRARIES} +) + +INSTALL(PROGRAMS psyn DESTINATION bin) +#INSTALL(FILES jsweeper.ui DESTINATION share/jsweeper) diff --git a/engine.c b/engine.c new file mode 100644 index 0000000..4c4de16 --- /dev/null +++ b/engine.c @@ -0,0 +1,81 @@ +#include +#include +#include +#include +#include "env.h" +#include "osc.h" +#include "voice.h" +#include "engine.h" +#include "psyn.h" + +static double _freqs[128]; + +void engine_init() +{ + uint32_t i; + + for (i = 0; i < 128; i++) { + _freqs[i] = 440.0 * pow(2.0, (i - 69.0) / 12.0); + } + + osc_init(); + env_init(&_env, 0.0125, 0.025, 5.0, 0.0, 0.25); + env_init(&_env2, 0.0125, 0.025, 0.5, 0.0, 0.25); +} + +void engine_run(struct engine_t *engine, uint32_t samples, float *left, float *right) +{ + struct voice_t *v; + uint32_t pos; + uint32_t i; + + for (pos = 0; pos < samples; pos++) { + left[pos] = 0.0; + right[pos] = 0.0; + + for (i = 0; i < NUM_LFO; i++) { + osc_tick(&engine->osc[i]); + } + + v = engine->voice; + for (i = 0; i < NUM_POLYPHONY; i++, v++) { + if (!v->playing) continue; + + voice_run(v, 1, left + pos, right + pos); + } + } +} + +void engine_startvoice(struct engine_t *engine, uint8_t note, uint8_t velocity) +{ + struct voice_t *v = engine->voice; + uint32_t i; + + for (i = 0; i < NUM_POLYPHONY; i++, v++) { + if (v->playing) continue; + + v->playing = true; + v->sample = 0; + v->released = 0; + v->note = note; + v->velocity = velocity / 127.0; + + osc_setfreq(&v->osc[0], _freqs[note]); + osc_setfreq(&v->osc[1], _freqs[note] * 2); + osc_setfreq(&v->osc[2], _freqs[note] * 5); + + return; + } +} + +void engine_endvoice(struct engine_t *engine, uint8_t note, uint8_t velocity) +{ + struct voice_t *v = engine->voice; + uint32_t i; + + for (i = 0; i < NUM_POLYPHONY; i++, v++) { + if (v->released > 0 || v->note != note) continue; + + v->released = v->sample; + } +} diff --git a/engine.h b/engine.h new file mode 100644 index 0000000..b9c1332 --- /dev/null +++ b/engine.h @@ -0,0 +1,14 @@ + +#define NUM_LFO 4 +#define NUM_POLYPHONY 32 + +struct engine_t +{ + struct osc_t osc[NUM_LFO]; + struct voice_t voice[NUM_POLYPHONY]; +}; + +void engine_init(); +void engine_run(struct engine_t *engine, uint32_t samples, float *left, float *right); +void engine_startvoice(struct engine_t *engine, uint8_t note, uint8_t velocity); +void engine_endvoice(struct engine_t *engine, uint8_t note, uint8_t velocity); diff --git a/env.c b/env.c new file mode 100644 index 0000000..9a0f8b5 --- /dev/null +++ b/env.c @@ -0,0 +1,42 @@ + +#include +#include "env.h" +#include "psyn.h" + +struct envelope_t _env; +struct envelope_t _env2; + +void env_init(struct envelope_t *env, double attack, double attack_hold, double decay, double sustain, double release) +{ + env->attack = attack; + env->attack_hold = attack_hold; + env->decay = decay; + env->sustain = sustain; + env->release = release; + + env->attack_s = env->attack * _sample_rate; + env->attack_hold_s = env->attack_hold * _sample_rate; + env->decay_s = env->decay * _sample_rate; + env->release_s = env->release * _sample_rate; +} + +double env_getamplitude(struct envelope_t *env, uint32_t sample, uint32_t released) +{ + double amplitude; + + if (sample < env->attack_s) { + amplitude = sample / env->attack_s; + } else if (sample < env->attack_hold_s) { + amplitude = 1.0; + } else if (sample < env->decay_s) { + amplitude = 1.0 - (sample - env->attack_hold_s) / (env->decay_s - env->attack_hold_s) * (1.0 - env->sustain); + } else { + amplitude = env->sustain; + } + + if (released > 0) { + amplitude *= (1.0 - (sample - released) / env->release_s); + } + + return amplitude; +} diff --git a/env.h b/env.h new file mode 100644 index 0000000..0064f10 --- /dev/null +++ b/env.h @@ -0,0 +1,23 @@ + +struct envelope_t +{ + /* 'Real world' AHDSR values */ + double attack; + double attack_hold; + double decay; + double sustain; + double release; + + /* AHDR values in samples */ + double attack_s; + double attack_hold_s; + double decay_s; + double release_s; +}; + +extern struct envelope_t _env; +extern struct envelope_t _env2; + +void env_init(struct envelope_t *env, double attack, double attack_hold, double decay, double sustain, double release); +double env_getamplitude(struct envelope_t *env, uint32_t sample, uint32_t released); + diff --git a/lv2_event.h b/lv2_event.h new file mode 100644 index 0000000..086c819 --- /dev/null +++ b/lv2_event.h @@ -0,0 +1,273 @@ +/* lv2_event.h - C header file for the LV2 events extension. + * + * Copyright (C) 2006-2007 Lars Luthman + * Copyright (C) 2008 Dave Robillard + * + * This header is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This header 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 Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this header; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 01222-1307 USA + */ + +#ifndef LV2_EVENT_H +#define LV2_EVENT_H + +#define LV2_EVENT_URI "http://lv2plug.in/ns/ext/event" +#define LV2_EVENT_AUDIO_STAMP 0 + +#include + +/** @file + * This header defines the code portion of the LV2 events extension with + * URI . + * + * Below, the URI prefix 'lv2ev' is assumed to expand to + * . + * + * This extension is a generic transport mechanism for time stamped events + * of any type (e.g. MIDI, OSC, ramps, etc). Each port can transport mixed + * events of any type; the type of events and timestamps are defined by a URI + * which is mapped to an integer by the host for performance reasons. + * + * This extension requires the host to support the LV2 URI Map extension. + * This requirement is implicit - a plugin does not have to list the URI Map + * feature as required or optional in its RDF data for the host to provide + * the URI Map LV2_Feature in the instantiation function. + * + * Any host which supports this extension MUST guarantee that any call to + * the LV2 URI Map uri_to_id function with the URI of this extension as the + * 'map' argument returns a value within the range of uint16_t. + */ + + +/** The best Pulses Per Quarter Note for tempo-based uint32_t timestmaps. + * Equal to 2^12 * 5 * 7 * 9 * 11 * 13 * 17, which is evenly divisble + * by all integers from 1 through 18 inclusive, and powers of 2 up to 2^12. + */ +static const uint32_t LV2_EVENT_PPQN = 3136573440U; + + +/** An LV2 event (header only). + * + * LV2 events are generic time-stamped containers for any type of event. + * The type field defines the format of a given event's contents. + * + * This struct defines the header of an LV2 event. An LV2 event, as specified + * in this extension, is a single chunk of POD (plain old data), usually + * contained in a flat buffer (see LV2_EventBuffer below), that can be safely + * copied using a simple: + * + * memcpy(ev_copy, ev, sizeof(LV2_Event) + ev->size); (or equivalent) + * + * However, events with event type 0 need to be handled specially (see below). + */ +typedef struct { + + /** The frames portion of timestamp. The units used here can optionally + * be set for a port (with lv2ev:supportsTimeStamp and related + * properties), otherwise this is audio frames, corresponding to the + * sample_count parameter of the LV2 run method (e.g. frame 0 is the + * first frame for that call to run). + */ + uint32_t frames; + + /** The sub-frames portion of timestamp. The units used here can + * optionally be set for a port (with lv2ev:supportsTimeStamp and + * related properties), otherwise this is 1/(2^32) of an audio frame. + */ + uint32_t subframes; + + /** The type of this event, as a number which represents some URI + * defining an event type. This value MUST be some value previously + * returned from a call to the uri_to_id function defined in the LV2 + * URI map extension (see lv2_uri_map.h). + * + * There are special rules which must be followed depending on the type + * of an event. If the plugin recognizes an event type, the definition + * of that event type will describe how to interpret the event, and + * any required behaviour. Otherwise, if the type is 0, this event is a + * non-POD event and lv2_event_unref MUST be called if the event is + * 'dropped' (see below). Even if the plugin does not understand an + * event, it may pass the event through to an output by simply copying + * (and NOT calling lv2_event_unref). These rules are designed to + * allow for generic event handling plugins and large non-POD events, + * but with minimal hassle on simple plugins that "don't care" about + * these more advanced features. + * + * Plugins should not interpret type 0 events in any way unless + * specified by another extension. + */ + uint16_t type; + + /** The size of the data portion of this event in bytes, which + * immediately follows. The header size (12 bytes) is not included in + * this value. + */ + uint16_t size; + + /* size bytes of data follow here */ + +} LV2_Event; + + + +/** A buffer of LV2 events (header only). + * + * This struct is used as the port buffer for LV2 plugin ports that have the + * port class lv2ev:EventPort. + * + * The data member points to a buffer that contains an event header (defined + * by struct* LV2_Event), followed by that event's contents (padded to 64 bits), + * followed by another header, etc: + * + * | | | | | | | + * | | | | | | | | | | | | | | | | | | | | | | | | | + * |FRAMES |SUBFRMS|TYP|LEN|DATA..DATA..PAD|FRAMES | ... + */ +typedef struct { + + /** The contents of the event buffer. This may or may not reside in the + * same block of memory as this header, plugins must not assume either. + * The host guarantees this points to at least capacity bytes of + * allocated memory (though only size bytes of that are valid events). + */ + uint8_t* data; + + /** The size of this event header in bytes (including everything). + * + * This is to allow for extending this header in the future without + * breaking binary compatibility. Whenever this header is copied, + * it MUST be done using this field (and NOT the sizeof this struct). + */ + uint16_t header_size; + + /** The type of the time stamps for events in this buffer. + * As a special exception, '0' always means audio frames and subframes + * (1/UINT32_MAX'th of a frame) in the sample rate passed to instantiate. + * INPUTS: The host must set this field to the numeric ID of some URI + * defining the meaning of the frames/subframes fields of contained + * events (obtained by the LV2 URI Map uri_to_id function with the URI + * of this extension as the 'map' argument, see lv2_uri_map.h). + * The host must never pass a plugin a buffer which uses a stamp type + * the plugin does not 'understand'. The value of this field must + * never change, except when connect_port is called on the input + * port, at which time the host MUST have set the stamp_type field to + * the value that will be used for all subsequent run calls. + * OUTPUTS: The plugin may set this to any value that has been returned + * from uri_to_id with the URI of this extension for a 'map' argument. + * When connected to a buffer with connect_port, output ports MUST set + * this field to the type of time stamp they will be writing. On any + * call to connect_port on an event input port, the plugin may change + * this field on any output port, it is the responsibility of the host + * to check if any of these values have changed and act accordingly. + */ + uint16_t stamp_type; + + /** The number of events in this buffer. + * INPUTS: The host must set this field to the number of events + * contained in the data buffer before calling run(). + * The plugin must not change this field. + * OUTPUTS: The plugin must set this field to the number of events it + * has written to the buffer before returning from run(). + * Any initial value should be ignored by the plugin. + */ + uint32_t event_count; + + /** The size of the data buffer in bytes. + * This is set by the host and must not be changed by the plugin. + * The host is allowed to change this between run() calls. + */ + uint32_t capacity; + + /** The size of the initial portion of the data buffer containing data. + * INPUTS: The host must set this field to the number of bytes used + * by all events it has written to the buffer (including headers) + * before calling the plugin's run(). + * The plugin must not change this field. + * OUTPUTS: The plugin must set this field to the number of bytes + * used by all events it has written to the buffer (including headers) + * before returning from run(). + * Any initial value should be ignored by the plugin. + */ + uint32_t size; + +} LV2_Event_Buffer; + + +/** Opaque pointer to host data. */ +typedef void* LV2_Event_Callback_Data; + + +/** The data field of the LV2_Feature for this extension. + * + * To support this extension the host must pass an LV2_Feature struct to the + * plugin's instantiate method with URI "http://lv2plug.in/ns/ext/event" + * and data pointed to an instance of this struct. The plugin does not have + * to list that URI as a required or optional feature in its RDF data - the + * host MUST pass this LV2_Feature if the plugin has an port of class + * lv2ev:EventPortthat the host connects to. + */ +typedef struct { + + /** Opaque pointer to host data. + * + * The plugin MUST pass this to any call to functions in this struct. + * Otherwise, it must not be interpreted in any way. + */ + LV2_Event_Callback_Data callback_data; + + /** Take a reference to a non-POD event. + * + * If a plugin receives an event with type 0, it means the event is a + * pointer to some object in memory and not a flat sequence of bytes + * in the buffer. When receiving a non-POD event, the plugin already + * has an implicit reference to the event. If the event is stored AND + * passed to an output, or passed to two outputs, lv2_event_ref MUST + * be called on that event. + * If the event is only stored OR passed through, this is not necessary + * (as the plugin already has 1 implicit reference). + * + * The host guarantees that this function is realtime safe if the + * plugin is. + * + * @param event An event received at an input that will be copied to + * more than one output or duplicated in some other way. + * + * PLUGINS THAT VIOLATE THESE RULES MAY CAUSE CRASHES AND MEMORY LEAKS. + */ + uint32_t (*lv2_event_ref)(LV2_Event_Callback_Data callback_data, + LV2_Event* event); + + /** Drop a reference to a non-POD event. + * + * If a plugin receives an event with type 0, it means the event is a + * pointer to some object in memory and not a flat sequence of bytes + * in the buffer. If the plugin does not pass the event through to + * an output or store it internally somehow, it MUST call this function + * on the event (more information on using non-POD events below). + * + * The host guarantees that this function is realtime safe if the + * plugin is. + * + * @param event An event received at an input that will not be copied to + * an output or stored in any way. + * + * PLUGINS THAT VIOLATE THESE RULES MAY CAUSE CRASHES AND MEMORY LEAKS. + */ + uint32_t (*lv2_event_unref)(LV2_Event_Callback_Data callback_data, + LV2_Event* event); + +} LV2_Event_Feature; + + +#endif // LV2_EVENT_H + diff --git a/lv2_event_helpers.h b/lv2_event_helpers.h new file mode 100644 index 0000000..c31d0cd --- /dev/null +++ b/lv2_event_helpers.h @@ -0,0 +1,262 @@ +/* lv2_event_helpers.h - Helper functions for the LV2 events extension. + * + * Copyright (C) 2008 Dave Robillard + * + * This header is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This header 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 Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this header; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 01222-1307 USA + */ + +#ifndef LV2_EVENT_HELPERS_H +#define LV2_EVENT_HELPERS_H + +#include +#include +#include +#include +#include + +#include "lv2_event.h" + +/** @file + * This header defines some helper functions for the the LV2 events extension + * with URI ('lv2ev'). + * + * These functions are provided for convenience only, use of them is not + * required for supporting lv2ev (i.e. the events extension is defined by the + * raw buffer format described in lv2_event.h and NOT by this API). + * + * Note that these functions are all static inline which basically means: + * do not take the address of these functions. */ + + +/** Pad a size to 64 bits (for event sizes) */ +static inline uint16_t +lv2_event_pad_size(uint16_t size) +{ + return (size + 7) & (~7); +} + + +/** Initialize (empty, reset..) an existing event buffer. + * The contents of buf are ignored entirely and overwritten, except capacity + * which is unmodified. */ +static inline void +lv2_event_buffer_reset(LV2_Event_Buffer* buf, uint16_t stamp_type, uint8_t *data) +{ + buf->data = data; + buf->header_size = sizeof(LV2_Event_Buffer); + buf->stamp_type = stamp_type; + buf->event_count = 0; + buf->size = 0; +} + + +/** Allocate a new, empty event buffer. */ +static inline LV2_Event_Buffer* +lv2_event_buffer_new(uint32_t capacity, uint16_t stamp_type) +{ + LV2_Event_Buffer* buf = (LV2_Event_Buffer*)malloc(sizeof(LV2_Event_Buffer) + capacity); + if (buf != NULL) { + buf->capacity = capacity; + lv2_event_buffer_reset(buf, stamp_type, (uint8_t *)(buf + 1)); + return buf; + } else { + return NULL; + } +} + + +/** An iterator over an LV2_Event_Buffer. + * + * Multiple simultaneous read iterators over a single buffer is fine, + * but changing the buffer invalidates all iterators (e.g. RW Lock). */ +typedef struct { + LV2_Event_Buffer* buf; + uint32_t offset; +} LV2_Event_Iterator; + + +/** Reset an iterator to point to the start of @a buf. + * @return True if @a iter is valid, otherwise false (buffer is empty) */ +static inline bool +lv2_event_begin(LV2_Event_Iterator* iter, + LV2_Event_Buffer* buf) +{ + iter->buf = buf; + iter->offset = 0; + return (buf->size > 0); +} + + +/** Check if @a iter is valid.. + * @return True if @a iter is valid, otherwise false (past end of buffer) */ +static inline bool +lv2_event_is_valid(LV2_Event_Iterator* iter) +{ + return (iter->offset < iter->buf->size); +} + + +/** Advance @a iter forward one event. + * @a iter must be valid. + * @return True if @a iter is valid, otherwise false (reached end of buffer) */ +static inline bool +lv2_event_increment(LV2_Event_Iterator* iter) +{ + assert(lv2_event_is_valid(iter)); + + LV2_Event* const ev = (LV2_Event*)( + (uint8_t*)iter->buf->data + iter->offset); + + iter->offset += lv2_event_pad_size(sizeof(LV2_Event) + ev->size); + + return true; +} + + +/** Dereference an event iterator (get the event currently pointed at). + * @a iter must be valid. + * @a data if non-NULL, will be set to point to the contents of the event + * returned. + * @return A Pointer to the event @a iter is currently pointing at, or NULL + * if the end of the buffer is reached (in which case @a data is + * also set to NULL). */ +static inline LV2_Event* +lv2_event_get(LV2_Event_Iterator* iter, + uint8_t** data) +{ + assert(lv2_event_is_valid(iter)); + + LV2_Event* const ev = (LV2_Event*)( + (uint8_t*)iter->buf->data + iter->offset); + + if (data) + *data = (uint8_t*)ev + sizeof(LV2_Event); + + return ev; +} + + +/** Get the type of the non-POD event referenced by an event iterator. + * @a iter must be valid. + * @return The type of the non-POD event, or 0 if the event is not non-POD. */ +static inline uint16_t +lv2_event_get_nonpod_type(LV2_Event_Iterator* iter) +{ + assert(lv2_event_is_valid(iter)); + + LV2_Event* const ev = (LV2_Event*)( + (uint8_t*)iter->buf->data + iter->offset); + + if (ev->type != 0 || ev->size < 2) + return 0; + + return *(uint16_t*)((uint8_t*)ev + sizeof(LV2_Event)); +} + + +/** Write an event at @a iter. + * The event (if any) pointed to by @iter will be overwritten, and @a iter + * incremented to point to the following event (i.e. several calls to this + * function can be done in sequence without twiddling iter in-between). + * @return True if event was written, otherwise false (buffer is full). */ +static inline bool +lv2_event_write(LV2_Event_Iterator* iter, + uint32_t frames, + uint32_t subframes, + uint16_t type, + uint16_t size, + const uint8_t* data) +{ + if (iter->buf->capacity - iter->buf->size < sizeof(LV2_Event) + size) + return false; + + LV2_Event* const ev = (LV2_Event*)( + (uint8_t*)iter->buf->data + iter->offset); + + ev->frames = frames; + ev->subframes = subframes; + ev->type = type; + ev->size = size; + memcpy((uint8_t*)ev + sizeof(LV2_Event), data, size); + ++iter->buf->event_count; + + size = lv2_event_pad_size(sizeof(LV2_Event) + size); + iter->buf->size += size; + iter->offset += size; + + return true; +} + + +/** Reserve space for an event in the buffer and return a pointer to + * the memory where the caller can write the event data, or NULL if there + * is not enough room in the buffer. */ +static inline uint8_t* +lv2_event_reserve(LV2_Event_Iterator* iter, + uint32_t frames, + uint32_t subframes, + uint16_t type, + uint16_t size) +{ + size = lv2_event_pad_size(size); + if (iter->buf->capacity - iter->buf->size < sizeof(LV2_Event) + size) + return NULL; + + LV2_Event* const ev = (LV2_Event*)((uint8_t*)iter->buf->data + + iter->offset); + + ev->frames = frames; + ev->subframes = subframes; + ev->type = type; + ev->size = size; + ++iter->buf->event_count; + + size = lv2_event_pad_size(sizeof(LV2_Event) + size); + iter->buf->size += size; + iter->offset += size; + + return (uint8_t*)ev + sizeof(LV2_Event); +} + + +/** Write an event at @a iter. + * The event (if any) pointed to by @iter will be overwritten, and @a iter + * incremented to point to the following event (i.e. several calls to this + * function can be done in sequence without twiddling iter in-between). + * @return True if event was written, otherwise false (buffer is full). */ +static inline bool +lv2_event_write_event(LV2_Event_Iterator* iter, + const LV2_Event* ev, + const uint8_t* data) +{ + if (iter->buf->capacity - iter->buf->size < sizeof(LV2_Event) + ev->size) + return false; + + LV2_Event* const write_ev = (LV2_Event*)( + (uint8_t*)iter->buf->data + iter->offset); + + *write_ev = *ev; + memcpy((uint8_t*)write_ev + sizeof(LV2_Event), data, ev->size); + ++iter->buf->event_count; + + const uint16_t size = lv2_event_pad_size(sizeof(LV2_Event) + ev->size); + iter->buf->size += size; + iter->offset += size; + + return true; +} + +#endif // LV2_EVENT_HELPERS_H + diff --git a/lv2_uri_map.h b/lv2_uri_map.h new file mode 100644 index 0000000..1c2f5b0 --- /dev/null +++ b/lv2_uri_map.h @@ -0,0 +1,88 @@ +/* lv2_uri_map.h - C header file for the LV2 URI Map extension. + * + * Copyright (C) 2008 Dave Robillard + * + * This header is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This header 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 Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this header; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 01222-1307 USA + */ + +#ifndef LV2_URI_MAP_H +#define LV2_URI_MAP_H + +#define LV2_URI_MAP_URI "http://lv2plug.in/ns/ext/uri-map" + +#include + +/** @file + * This header defines the LV2 URI Map extension with the URI + * (preferred prefix 'lv2urimap'). + * + * This extension defines a simple mechanism for plugins to map URIs to + * integers, usually for performance reasons (e.g. processing events + * typed by URIs in real time). The expected use case is for plugins to + * map URIs to integers for things they 'understand' at instantiation time, + * and store those values for use in the audio thread without doing any string + * comparison. This allows the extensibility of RDF with the performance of + * integers (or centrally defined enumerations). + */ + + +/** Opaque pointer to host data. */ +typedef void* LV2_URI_Map_Callback_Data; + + +/** The data field of the LV2_Feature for this extension. + * + * To support this feature the host must pass an LV2_Feature struct to the + * plugin's instantiate method with URI "http://lv2plug.in/ns/ext/uri-map" + * and data pointed to an instance of this struct. + */ +typedef struct { + + /** Opaque pointer to host data. + * + * The plugin MUST pass this to any call to functions in this struct. + * Otherwise, it must not be interpreted in any way. + */ + LV2_URI_Map_Callback_Data callback_data; + + /** Get the numeric ID of a URI from the host. + * + * @param callback_data Must be the callback_data member of this struct. + * @param map The 'context' of this URI. Certain extensions may define a + * URI that must be passed here with certain restrictions on the + * return value (e.g. limited range). This value may be NULL if + * the plugin needs an ID for a URI in general. + * @param uri The URI to be mapped to an integer ID. + * + * This function is referentially transparent - any number of calls with + * the same arguments is guaranteed to return the same value over the life + * of a plugin instance (though the same URI may return different values + * with a different map parameter). However, this function is not + * necessarily very fast: plugins should cache any IDs they might need in + * performance critical situations. + * The return value 0 is reserved and means an ID for that URI could not + * be created for whatever reason. Extensions may define more precisely + * what this means, but in general plugins should gracefully handle 0 + * and consider whatever they wanted the URI for "unsupported". + */ + uint32_t (*uri_to_id)(LV2_URI_Map_Callback_Data callback_data, + const char* map, + const char* uri); + +} LV2_URI_Map_Feature; + + +#endif // LV2_URI_MAP_H + diff --git a/manifest.ttl b/manifest.ttl new file mode 100644 index 0000000..6222f5f --- /dev/null +++ b/manifest.ttl @@ -0,0 +1,7 @@ +@prefix lv2: . +@prefix rdfs: . + + + a lv2:Plugin, lv2:InstrumentPlugin; + lv2:binary ; + rdfs:seeAlso . diff --git a/osc.c b/osc.c new file mode 100644 index 0000000..218cac9 --- /dev/null +++ b/osc.c @@ -0,0 +1,32 @@ +#include +#include +#include "psyn.h" +#include "osc.h" + +double _sin_table[LOOKUP_SAMPLES + 1]; +double _saw_table[LOOKUP_SAMPLES + 1]; +double _tri_table[LOOKUP_SAMPLES + 1]; + +void osc_init() +{ + int i; + + for (i = 0; i <= LOOKUP_SAMPLES; i++) { + _sin_table[i] = sin(2 * M_PI * (double)i / LOOKUP_SAMPLES); + } + for (i = 0; i <= LOOKUP_SAMPLES; i++) { + _saw_table[i] = 1.0 - ((double)i / (LOOKUP_SAMPLES / 2)); + } + for (i = 0; i <= LOOKUP_SAMPLES; i++) { + _tri_table[i] = (double)i / (LOOKUP_SAMPLES / 4); + if (_tri_table[i] > 1.0) _tri_table[i] = 2.0 - _tri_table[i]; + if (_tri_table[i] < -1.0) _tri_table[i] = -2.0 - _tri_table[i]; + } +} + +void osc_setfreq(struct osc_t *osc, double freq) +{ + osc->freq = freq; + osc->step = freq / _sample_rate * LOOKUP_SAMPLES; +} + diff --git a/osc.h b/osc.h new file mode 100644 index 0000000..d25d03c --- /dev/null +++ b/osc.h @@ -0,0 +1,32 @@ + +#define LOOKUP_SAMPLES 360 + +extern double _sin_table[LOOKUP_SAMPLES + 1]; +extern double _saw_table[LOOKUP_SAMPLES + 1]; +extern double _tri_table[LOOKUP_SAMPLES + 1]; + +struct osc_t +{ + double freq; + double step; + double ramp; + + double sin; + double saw; + double tri; +}; + +void osc_init(); +void osc_setfreq(struct osc_t *osc, double freq); + +static inline void osc_tick(struct osc_t *osc) +{ + osc->ramp += osc->step; + if (osc->ramp > LOOKUP_SAMPLES) osc->ramp -= LOOKUP_SAMPLES; + + uint32_t pos = floor(osc->ramp); + + osc->sin = _sin_table[pos]; + osc->saw = _saw_table[pos]; + osc->tri = _tri_table[pos]; +} diff --git a/psyn.c b/psyn.c new file mode 100644 index 0000000..8f12d3d --- /dev/null +++ b/psyn.c @@ -0,0 +1,155 @@ +#include +#include +#include +#include +#include "lv2_event.h" +#include "lv2_event_helpers.h" +#include "lv2_uri_map.h" +#include "osc.h" +#include "voice.h" +#include "engine.h" +#include "psyn.h" + +double _sample_rate; + +struct psyn_t +{ + LV2_Event_Buffer *events; + LV2_Event_Feature *event_ref; + int midi_event_id; + float *out_l; + float *out_r; + + struct engine_t eng; +}; + +static void psyn_init(struct psyn_t *psyn, uint32_t sample_rate) +{ + _sample_rate = sample_rate; + + engine_init(); +} + +static LV2_Handle instantiate( + const LV2_Descriptor *descriptor, + double sample_rate, + const char *bundle_path, + const LV2_Feature * const *host_features) +{ + struct psyn_t *psyn; + LV2_URI_Map_Feature *map_feature; + int i; + + psyn = malloc(sizeof *psyn); + memset(psyn, 0, sizeof *psyn); + + psyn_init(psyn, sample_rate); + + for (i = 0; host_features[i]; i++) { + if (!strcmp(host_features[i]->URI, "http://lv2plug.in/ns/ext/uri-map")) { + map_feature = host_features[i]->data; + + psyn->midi_event_id = map_feature->uri_to_id(map_feature->callback_data, "http://lv2plug.in/ns/ext/event", "http://lv2plug.in/ns/ext/midi#MidiEvent"); + } else if (!strcmp(host_features[i]->URI, "http://lv2plug.in/ns/ext/event")) { + psyn->event_ref = host_features[i]->data; + } + } + + if (psyn->midi_event_id == 0 || psyn->event_ref == NULL) { + printf("psyn instantiate failed, leaving\n"); + return NULL; + } + + return (LV2_Handle)psyn; +} + +static void connect_port(LV2_Handle lv2instance, uint32_t port, void *data) +{ + struct psyn_t *psyn = (struct psyn_t *)lv2instance; + + switch (port) { + case 0: + psyn->events = data; + break; + case 1: + psyn->out_l = data; + break; + case 2: + psyn->out_r = data; + break; + } +} + +static void cleanup(LV2_Handle lv2instance) +{ + struct psyn_t *psyn = (struct psyn_t *)lv2instance; + free(psyn); +} + +static void run(LV2_Handle lv2instance, uint32_t sample_count) +{ + struct psyn_t *psyn = (struct psyn_t *)lv2instance; + uint32_t frame = 0; + LV2_Event *ev = NULL; + LV2_Event_Iterator iterator; + + lv2_event_begin(&iterator, psyn->events); + + if (lv2_event_is_valid(&iterator)) ev = lv2_event_get(&iterator, NULL); + + while (frame < sample_count) { + uint32_t to; + + if (ev != NULL) { + to = ev->frames; + } else { + to = sample_count; + } + + engine_run(&psyn->eng, to - frame, psyn->out_l + frame, psyn->out_r + frame); + frame = to; + + if (ev != NULL) { + if (ev->type == 0) { + psyn->event_ref->lv2_event_unref(psyn->event_ref->callback_data, ev); + } else if (ev->type == psyn->midi_event_id && ev->size == 3) { + uint8_t *data = (uint8_t *)(ev + 1); + + switch (data[0] & 0xF0) { + case 0x80: + engine_endvoice(&psyn->eng, data[1], data[2]); + break; + + case 0x90: + engine_startvoice(&psyn->eng, data[1], data[2]); + break; + } + } + + lv2_event_increment(&iterator); + if (lv2_event_is_valid(&iterator)) { + ev = lv2_event_get(&iterator, NULL); + } else { + ev = NULL; + } + } + } +} + +static LV2_Descriptor g_lv2descriptor = +{ + .URI = "http://fuzzle.org/~petern/psyn/1", + .instantiate = &instantiate, + .connect_port = &connect_port, + .run = &run, + .cleanup = &cleanup, +}; + +const LV2_Descriptor *lv2_descriptor(uint32_t index) +{ + if (index == 0) { + return &g_lv2descriptor; + } + + return NULL; +} diff --git a/psyn.h b/psyn.h new file mode 100644 index 0000000..d3b397b --- /dev/null +++ b/psyn.h @@ -0,0 +1,2 @@ +extern double _sample_rate; + diff --git a/psyn.ttl b/psyn.ttl new file mode 100644 index 0000000..d2d0738 --- /dev/null +++ b/psyn.ttl @@ -0,0 +1,37 @@ +@prefix lv2: . +@prefix foaf: . +@prefix doap: . +@prefix lv2ev: . + + + a lv2:Plugin; + doap:maintainer [ + foaf:name "Peter Nelson"; + foaf:homepage ; + foaf:mbox ; + ]; + doap:name "PSynth"; + doap:license ; + + lv2:port [ + a lv2ev:EventPort; + a lv2:InputPort; + lv2ev:supportsEvent ; + lv2:index 0; + lv2:symbol "in"; + lv2:name "MIDI Input"; + ], + [ + a lv2:OutputPort, lv2:AudioPort; + lv2:datatype lv2:float; + lv2:index 1; + lv2:symbol "out L"; + lv2:name "Audio Output L"; + ], + [ + a lv2:OutputPort, lv2:AudioPort; + lv2:datatype lv2:float; + lv2:index 2; + lv2:symbol "out R"; + lv2:name "Audio Output R"; + ]. diff --git a/voice.c b/voice.c new file mode 100644 index 0000000..9a98d4c --- /dev/null +++ b/voice.c @@ -0,0 +1,65 @@ + +#include +#include +#include +#include "osc.h" +#include "env.h" +#include "voice.h" +#include "psyn.h" + +static inline void voice_tick(struct voice_t *voice) +{ + unsigned i; + for (i = 0; i < VOICE_OSCILLATORS; i++) { + osc_tick(voice->osc + i); + } + + voice->sample++; +} + +static double _R = 2.7; +static double _C = 4.3; + +static inline void voice_filter(struct voice_t *voice, float l, float r) +{ + double k = 1.0 / (_R * _C) / _sample_rate; + + voice->last_l += k * (l - voice->last_l); + voice->last_r += k * (r - voice->last_r); +} + +void voice_run(struct voice_t *voice, uint32_t samples, float *left, float *right) +{ +// uint32_t pos; + double amplitude; + double l; + double r; + + //for (pos = 0; pos < samples; pos++) { + voice_tick(voice); + + amplitude = env_getamplitude(&_env, voice->sample, voice->released); + + if (amplitude <= 0.0001) { + voice->playing = false; + return; + } + + amplitude *= voice->velocity; + + l = amplitude * voice->osc[0].saw; + r = amplitude * voice->osc[0].saw; + + amplitude = env_getamplitude(&_env2, voice->sample, voice->released) * voice->velocity * voice->velocity * 1.5; + + if (amplitude > 0.0) { + l *= voice->osc[1].tri * (1.0 + voice->osc[2].sin * 0.25); + r *= voice->osc[1].tri * (1.0 + voice->osc[2].sin * 0.25); + } + + voice_filter(voice, l, r); + + *left += voice->last_l; + *right += voice->last_r; +// } +} diff --git a/voice.h b/voice.h new file mode 100644 index 0000000..ad6f179 --- /dev/null +++ b/voice.h @@ -0,0 +1,17 @@ +#define VOICE_OSCILLATORS 4 + +struct voice_t +{ + bool playing; + uint8_t note; + uint32_t sample; + uint32_t released; + + double velocity; + struct osc_t osc[VOICE_OSCILLATORS]; + + double last_l; + double last_r; +}; + +void voice_run(struct voice_t *voice, uint32_t samples, float *left, float *right);