Initial checkin

remotes/origin/HEAD
Peter Nelson 2010-01-13 10:35:51 +00:00
commit a0d54946b1
16 changed files with 1196 additions and 0 deletions

66
CMakeLists.txt 100644
View File

@ -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)

81
engine.c 100644
View File

@ -0,0 +1,81 @@
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <math.h>
#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;
}
}

14
engine.h 100644
View File

@ -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);

42
env.c 100644
View File

@ -0,0 +1,42 @@
#include <stdint.h>
#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;
}

23
env.h 100644
View File

@ -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);

273
lv2_event.h 100644
View File

@ -0,0 +1,273 @@
/* lv2_event.h - C header file for the LV2 events extension.
*
* Copyright (C) 2006-2007 Lars Luthman <lars.luthman@gmail.com>
* Copyright (C) 2008 Dave Robillard <dave@drobilla.net>
*
* 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 <stdint.h>
/** @file
* This header defines the code portion of the LV2 events extension with
* URI <http://lv2plug.in/ns/ext/event>.
*
* Below, the URI prefix 'lv2ev' is assumed to expand to
* <http://lv2plug.in/ns/ext/event#>.
*
* 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

262
lv2_event_helpers.h 100644
View File

@ -0,0 +1,262 @@
/* lv2_event_helpers.h - Helper functions for the LV2 events extension.
*
* Copyright (C) 2008 Dave Robillard <dave@drobilla.net>
*
* 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 <assert.h>
#include <stdbool.h>
#include <malloc.h>
#include <stdint.h>
#include <string.h>
#include "lv2_event.h"
/** @file
* This header defines some helper functions for the the LV2 events extension
* with URI <http://lv2plug.in/ns/ext/event> ('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

88
lv2_uri_map.h 100644
View File

@ -0,0 +1,88 @@
/* lv2_uri_map.h - C header file for the LV2 URI Map extension.
*
* Copyright (C) 2008 Dave Robillard <dave@drobilla.net>
*
* 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 <stdint.h>
/** @file
* This header defines the LV2 URI Map extension with the URI
* <http://lv2plug.in/ns/ext/uri-map> (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

7
manifest.ttl 100644
View File

@ -0,0 +1,7 @@
@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
<http://fuzzle.org/~petern/psyn/1>
a lv2:Plugin, lv2:InstrumentPlugin;
lv2:binary <psyn.so>;
rdfs:seeAlso <psyn.ttl>.

32
osc.c 100644
View File

@ -0,0 +1,32 @@
#include <stdint.h>
#include <math.h>
#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;
}

32
osc.h 100644
View File

@ -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];
}

155
psyn.c 100644
View File

@ -0,0 +1,155 @@
#include <stdint.h>
#include <stdio.h>
#include <math.h>
#include <lv2.h>
#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;
}

2
psyn.h 100644
View File

@ -0,0 +1,2 @@
extern double _sample_rate;

37
psyn.ttl 100644
View File

@ -0,0 +1,37 @@
@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix doap: <http://usefulinc.com/ns/doap#> .
@prefix lv2ev: <http://lv2plug.in/ns/ext/event#> .
<http://fuzzle.org/~petern/psyn/1>
a lv2:Plugin;
doap:maintainer [
foaf:name "Peter Nelson";
foaf:homepage <http://fuzzle.org/~petern/psyn/>;
foaf:mbox <mailto:peter@fuzzle.org>;
];
doap:name "PSynth";
doap:license <http://usefulinc.com/doap/licenses/gpl>;
lv2:port [
a lv2ev:EventPort;
a lv2:InputPort;
lv2ev:supportsEvent <http://lv2plug.in/ns/ext/midi#MidiEvent>;
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";
].

65
voice.c 100644
View File

@ -0,0 +1,65 @@
#include <stdint.h>
#include <stdbool.h>
#include <math.h>
#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;
// }
}

17
voice.h 100644
View File

@ -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);