Skip to content

Commit

Permalink
add DPF types; simplify midi docs
Browse files Browse the repository at this point in the history
  • Loading branch information
dromer committed Sep 22, 2024
1 parent 6d190c7 commit cb3183a
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 260 deletions.
25 changes: 19 additions & 6 deletions docs/03.gen.dpf.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,32 @@ Additionally you can use the special `[r __hv_dpf_bpm]` receiver to get the curr

## Parameter Types

In DPF a parameter can get an optional type configured. The default type is `float`. Other assignable types are `bool` - for toggling a value - and `trig` - for momentary signals.
In DPF a parameter can get an optional type configured. The default type is `float`.

Other assignable types are `int` - or whole numbers, `bool` - for toggling a value, and `trig` - for momentary signals.

![dpf](img/docs_param_type.png)

Using jinja the `v.attributes.type` can be evaluated for a specific string and different templating applied to the parameter. In DPF the extra types `bool` and `trig` result in the following plugin code:

```c++
parameter.hints = kParameterIsAutomable | kParameterIsBoolean;
parameter.hints = kParameterIsInteger
// or
parameter.hints = kParameterIsBoolean;
// or
parameter.hints = kParameterIsAutomable | kParameterIsTrigger;
parameter.hints = kParameterIsTrigger;
```

Other special types can give additional information to the host:

* `dB` - unit `dB` - min_value `-inf` label (assumes `0.0f`)
* `Hz` - unit `Hz`
* `log` - hints `kParameterIsLogarithmic`
* `log_hz` - unit `Hz` - hints `kParameterIsLogarithmic`

## Metadata

An accomponying metadata.json file can be included to set additional plugin settings.
An accompanying metadata.json file can be included to set additional plugin settings.

The `project` flag creates a `README.md` and `Makefile` in the root of the project output, but may conflict with other generators.

Expand All @@ -55,7 +66,7 @@ Each of these are optional and have either a default value or are entirely optio
"midi_input": 1,
"midi_output": 0,
"plugin_formats": [
"lv2_dsp",
"lv2_sep",
"vst2",
"vst3",
"clap",
Expand All @@ -67,7 +78,7 @@ Each of these are optional and have either a default value or are entirely optio

Other fields that the DPF metadata supports are:

* `port_groups` - If your plugin has more audio i/o that need to be grouped together.
* `port_groups` - If your plugin has more audio i/o that need to be grouped together or given Control Voltage status
* `enumerators` - Configure a set of parameters that cycle over `<key>: <value>`
* `enable_ui` - Boolean that creates a generic GUI. Requires `dpf-widgets` on the same level as `dpf`.
* `enable_modgui` - Boolean for use in MOD audio based systems.
Expand All @@ -77,6 +88,8 @@ Other fields that the DPF metadata supports are:
* `vst3_info` - String describing the VST3 plugin type.
* `clap_info` - List of strings describing the CLAP plugin type.

The full type specification can be found [here](https://github.com/Wasted-Audio/hvcc/blob/develop/hvcc/generators/types/meta.py).

An example plugin that uses some of these extended metadata is [WSTD 3Q](https://github.com/Wasted-Audio/wstd-3q).

## Notes
Expand Down
260 changes: 6 additions & 254 deletions docs/04.midi.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# MIDI I/O
# MIDI I/O

In PureData there are objects to handle interfacing with a machines MIDI device.
In PureData there are several objects to handle interfacing with MIDI.

**heavy** doesn't provide cross-platform implementation for MIDI I/O as the requirements tend to change depending on the platform or framework being used.

Expand Down Expand Up @@ -50,138 +50,11 @@ Here's the `DPF` implementation as an example.

## Handling MIDI Input

The MIDI input is called during the DPF `run()` loop where it receives `MidiEvent` messages:
The MIDI input is called during the DPF `run()` loop where it receives `MidiEvent` messages.

```cpp
#if DISTRHO_PLUGIN_WANT_MIDI_INPUT
// -------------------------------------------------------------------
// Midi Input handler

void {{class_name}}::handleMidiInput(uint32_t frames, const MidiEvent* midiEvents, uint32_t midiEventCount)
{
// Realtime events
// TODO: Continue and Reset

const TimePosition& timePos(getTimePosition());
const bool playing = timePos.playing;
if (playing != wasPlaying)
{
if (playing)
{
_context->sendMessageToReceiverV(HV_HASH_MIDIREALTIMEIN, 0,
"ff", (float) MIDI_RT_START);
} else {
_context->sendMessageToReceiverV(HV_HASH_MIDIREALTIMEIN, 0,
"ff", (float) MIDI_RT_STOP);
}
wasPlaying = playing;
}

if (playing && timePos.bbt.valid)
{
float samplesPerBeat = 60 * getSampleRate() / timePos.bbt.beatsPerMinute;
float samplesPerTick = samplesPerBeat / 24.0;

int i = 1;
while (samplesProcessed > samplesPerTick)
{
_context->sendMessageToReceiverV(HV_HASH_MIDIREALTIMEIN, i * 1000.0*samplesPerTick/getSampleRate(),
"ff", (float) MIDI_RT_CLOCK);
samplesProcessed -= samplesPerTick;
i++;
}
samplesProcessed += frames;
// printf("> ticks: %f - samples: %f \n", samplesPerTick, samplesProcessed);
}

// Midi events
for (uint32_t i=0; i < midiEventCount; ++i)
{
int status = midiEvents[i].data[0];
int command = status & 0xF0;
int channel = status & 0x0F;
int data1 = midiEvents[i].data[1];
int data2 = midiEvents[i].data[2];
[Source code (run loop)](https://github.com/Wasted-Audio/hvcc/blob/develop/hvcc/generators/c2dpf/templates/HeavyDPF.cpp#L201-L205)

// raw [midiin] messages
int dataSize = *(&midiEvents[i].data + 1) - midiEvents[i].data;

for (int i = 0; i < dataSize; ++i) {
_context->sendMessageToReceiverV(HV_HASH_MIDIIN, 1000.0*timePos.frame/getSampleRate(), "ff",
(float) midiEvents[i].data[i],
(float) channel);
}

if(mrtSet.find(status) != mrtSet.end())
{
_context->sendMessageToReceiverV(HV_HASH_MIDIREALTIMEIN, 1000.0*timePos.frame/getSampleRate(),
"ff", (float) status);
}

// typical midi messages
switch (command) {
case 0x80: { // note off
_context->sendMessageToReceiverV(HV_HASH_NOTEIN, 1000.0*timePos.frame/getSampleRate(), "fff",
(float) data1, // pitch
(float) 0, // velocity
(float) channel);
break;
}
case 0x90: { // note on
_context->sendMessageToReceiverV(HV_HASH_NOTEIN, 1000.0*timePos.frame/getSampleRate(), "fff",
(float) data1, // pitch
(float) data2, // velocity
(float) channel);
break;
}
case 0xB0: { // control change
_context->sendMessageToReceiverV(HV_HASH_CTLIN, 1000.0*timePos.frame/getSampleRate(), "fff",
(float) data2, // value
(float) data1, // cc number
(float) channel);
break;
}
case 0xC0: { // program change
_context->sendMessageToReceiverV(HV_HASH_PGMIN, 1000.0*timePos.frame/getSampleRate(), "ff",
(float) data1,
(float) channel);
break;
}
case 0xD0: { // aftertouch
_context->sendMessageToReceiverV(HV_HASH_TOUCHIN, 1000.0*timePos.frame/getSampleRate(), "ff",
(float) data1,
(float) channel);
break;
}
case 0xE0: { // pitch bend
// combine 7bit lsb and msb into 32bit int
hv_uint32_t value = (((hv_uint32_t) data2) << 7) | ((hv_uint32_t) data1);
_context->sendMessageToReceiverV(HV_HASH_BENDIN, 1000.0*timePos.frame/getSampleRate(), "ff",
(float) value,
(float) channel);
break;
}
default: break;
}
}
}
#endif


// -------------------------------------------------------------------
// DPF Plugin run() loop

#if DISTRHO_PLUGIN_WANT_MIDI_INPUT
void {{class_name}}::run(const float** inputs, float** outputs, uint32_t frames, const MidiEvent* midiEvents, uint32_t midiEventCount)
{
handleMidiInput(frames, midiEvents, midiEventCount);
#else
void {{class_name}}::run(const float** inputs, float** outputs, uint32_t frames)
{
#endif
_context->process((float**)inputs, outputs, frames);
}
```
[Source code (handleMidiInput)](https://github.com/Wasted-Audio/hvcc/blob/develop/hvcc/generators/c2dpf/templates/midiInput.cpp)

## Handling MIDI Output

Expand Down Expand Up @@ -213,125 +86,4 @@ Pd does not have specific Note Off events, so velocity 0 is assumed to be Note O

Bend assumes input values ranged `0 - 16383` for `[bendin]` (normal bend range), however as mentioned before `[bendout]` uses `-8192 to 8191` to stay compatible with pd-vanilla.

```cpp
#if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
// -------------------------------------------------------------------
// Midi Send handler
void {{class_name}}::handleMidiSend(uint32_t sendHash, const HvMessage *m)
{
MidiEvent midiSendEvent;
midiSendEvent.frame = 0;
midiSendEvent.dataExt = nullptr;
switch(sendHash){
case HV_HASH_NOTEOUT: // __hv_noteout
{
uint8_t note = hv_msg_getFloat(m, 0);
uint8_t velocity = hv_msg_getFloat(m, 1);
uint8_t ch = hv_msg_getFloat(m, 2);
ch %= 16; // drop any pd "ports"
midiSendEvent.size = 3;
if (velocity > 0){
midiSendEvent.data[0] = 0x90 | ch; // noteon
} else {
midiSendEvent.data[0] = 0x80 | ch; // noteoff
}
midiSendEvent.data[1] = note;
midiSendEvent.data[2] = velocity;
writeMidiEvent(midiSendEvent);
break;
}
case HV_HASH_CTLOUT:
{
uint8_t value = hv_msg_getFloat(m, 0);
uint8_t cc = hv_msg_getFloat(m, 1);
uint8_t ch = hv_msg_getFloat(m, 2);
ch %= 16;
midiSendEvent.size = 3;
midiSendEvent.data[0] = 0xB0 | ch; // send CC
midiSendEvent.data[1] = cc;
midiSendEvent.data[2] = value;
writeMidiEvent(midiSendEvent);
break;
}
case HV_HASH_PGMOUT:
{
uint8_t pgm = hv_msg_getFloat(m, 0);
uint8_t ch = hv_msg_getFloat(m, 1);
ch %= 16;
midiSendEvent.size = 2;
midiSendEvent.data[0] = 0xC0 | ch; // send Program Change
midiSendEvent.data[1] = pgm;
writeMidiEvent(midiSendEvent);
break;
}
case HV_HASH_TOUCHOUT:
{
uint8_t value = hv_msg_getFloat(m, 0);
uint8_t ch = hv_msg_getFloat(m, 1);
ch %= 16;
midiSendEvent.size = 2;
midiSendEvent.data[0] = 0xD0 | ch; // send Touch
midiSendEvent.data[1] = value;
writeMidiEvent(midiSendEvent);
break;
}
case HV_HASH_BENDOUT:
{
uint16_t value = hv_msg_getFloat(m, 0);
uint8_t lsb = value & 0x7F;
uint8_t msb = (value >> 7) & 0x7F;
uint8_t ch = hv_msg_getFloat(m, 1);
ch %= 16;
midiSendEvent.size = 3;
midiSendEvent.data[0] = 0xE0 | ch; // send Bend
midiSendEvent.data[1] = lsb;
midiSendEvent.data[2] = msb;
writeMidiEvent(midiSendEvent);
break;
}
case HV_HASH_MIDIOUT: // __hv_midiout
{
const uint8_t numElements = m->numElements;
if (numElements <=4 )
{
for (int i = 0; i < numElements; ++i)
{
midiSendEvent.data[i] = hv_msg_getFloat(m, i);
}
}
else
{
printf("> we do not support sysex yet \n");
break;
}
// unsigned char* rawData = new unsigned char;
// for (int i = 0; i < numElements; ++i) {
// rawData[i] = (uint8_t) hv_msg_getFloat(m, i);
// printf("> data: %d \n", rawData[i]);
// }
midiSendEvent.size = numElements;
// midiSendEvent.dataExt = (const uint8_t *) rawData;
writeMidiEvent(midiSendEvent);
break;
}
default:
break;
}
}
#endif
```
[Source code (handleMidiSend)](https://github.com/Wasted-Audio/hvcc/blob/develop/hvcc/generators/c2dpf/templates/midiOutput.cpp)

0 comments on commit cb3183a

Please sign in to comment.