commit
a0d54946b1
@ -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)
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
@ -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;
|
||||
}
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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>.
|
@ -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;
|
||||
}
|
||||
|
@ -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];
|
||||
}
|
@ -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;
|
||||
}
|
@ -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";
|
||||
].
|
@ -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;
|
||||
// }
|
||||
}
|
@ -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);
|
Loading…
Reference in new issue