Skip to content

Commit

Permalink
Feature/dpf improvements (Wasted-Audio#33)
Browse files Browse the repository at this point in the history
* initialize context and parameters in constructor

* just create a new context, otherwise this method is never called anyway

* try sending midi using sendHook

* try to set sendhook in the constructor

* some progress

* starting to work!

* some kind of midi

* nudge midi in and out in place

* update changelog

* DPF Midi improvements. Inputs, Outputs, docs, examples
  • Loading branch information
dromer authored Oct 23, 2021
1 parent 3208f01 commit baef1e8
Show file tree
Hide file tree
Showing 17 changed files with 584 additions and 194 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
CHANGELOG
=====

Next Release
-----

* Midi I/O extensions for DPF
* Midi bugfixes for [notein], [noteout], [pgmin], [touchin], [bendin]
* Midi docs update
* DPF minimal Midi examples

0.2.0
-----

Expand Down
4 changes: 3 additions & 1 deletion docs/03.gen.dpf.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Each [exposed parameter](02.getting_started.md#exposing-parameters) will automat
## MIDI Control
In order to receive MIDI note on and off events, as well as control change messages, the `[notein]` and `[ctlin]` objects should be used, respectively.

DPF supports all note/ctl/pgm/touch/bend I/O events. The implementation is further discussed in the [midi docs](04.midi.md)

![notein](img/docs_notein.png)

## Parameter Types
Expand Down Expand Up @@ -54,6 +56,6 @@ Each of these are optional and have either a default value or are entirely optio
```

## Notes
* The `[notein]` object is the only supported means of receiving MIDI note events (i.e. Note On and Note Off). Arguments to the object (e.g. to specify the channel number) will be ignored.
* The `[notein]` object is the only supported means of receiving MIDI note events (i.e. Note On and Note Off). Arguments to the object (e.g. to specify the channel number) will be ignored. Velocity of `0` will be assumed to mean Note Off
* The `[ctlin]` object is the only supported means of receiving MIDI control change events. Arguments to the object (e.g. to filter which CC event is delivered) will be ignored.
* If you are compiling from source, make sure to read the included `README.md` file in the root directory.
278 changes: 203 additions & 75 deletions docs/04.midi.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,104 +13,232 @@ Instead, it provides wrappers around these objects that route the data to specif

## Inputs

The following Pd objects are mapped to their corresponding heavy parameter.
The following Pd objects are mapped to their corresponding heavy parameter and internal hash.

| Pd object | heavy param |
| --------- | -------------- |
| [notein] | __hv_notein |
| [ctlin] | __hv_ctlin |
| [pgmin] | __hv_pgmin |
| [bendin] | __hv_bendin |
| [touchin] | __hv_touchin |
| Pd object | heavy param | heavy hash |
| --------- | -------------- | ---------- |
| [notein] | __hv_notein | 0x67e37ca3 |
| [ctlin] | __hv_ctlin | 0x41be0f9c |
| [pgmin] | __hv_pgmin | 0x2e1ea03d |
| [touchin] | __hv_touchin | 0x553925bd |
| [bendin] | __hv_bendin | 0x3083f0f7 |


## Outputs

The same principle applies for sending MIDI data out of the heavy context. If you add a [noteout] object there'll be a corresponding sendhook callback with a message containing the MIDI data sent by the patch.

| Pd object | heavy sendhook |
| --------- | -------------- |
| [noteout] | __hv_noteout |
| [ctlout] | __hv_ctlout |
| [pgmout] | __hv_pgmout |
| [bendout] | __hv_bendout |
| [touchout] | __hv_touchout |
| Pd object | heavy sendhook | heavy hash |
| --------- | -------------- |------------|
| [noteout] | __hv_noteout | 0xd1d4ac2 |
| [ctlout] | __hv_ctlout | 0xe5e2a040 |
| [pgmout] | __hv_pgmout | 0x8753e39e |
| [touchout] | __hv_touchout | 0x476d4387 |
| [bendout] | __hv_bendout | 0xe8458013 |

## Note!

`It is generally the users responsibility to convert to and from the MIDI byte data to the float values used by heavy.`

Some framework targets like [DPF](03.gen.dpf.md) already have implementations available. However, if you're integrating the C/C++ code on a custom platform then you'll need to provide your own conversion process.

Here's the `DPF` implementation as an example:
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:

```cpp
#if DISTRHO_PLUGIN_WANT_MIDI_INPUT
void {{class_name}}::handleMidiInput(uint32_t curEventIndex, const MidiEvent* midiEvents)
{
int status = midiEvents[curEventIndex].data[0];
int command = status & 0xF0;
int channel = status & 0x0F;
int data1 = midiEvents[curEventIndex].data[1];
int data2 = midiEvents[curEventIndex].data[2];

switch (command) {
case 0x80: { // note off
_context->sendMessageToReceiverV(HV_HASH_NOTEIN, 0, "fff",
(float) data1, // pitch
(float) 0, // velocity
(float) channel);
break;
}
case 0x90: { // note on
_context->sendMessageToReceiverV(HV_HASH_NOTEIN, 0, "fff",
(float) data1, // pitch
(float) data2, // velocity
(float) channel);
break;
}
case 0xB0: { // control change
_context->sendMessageToReceiverV(HV_HASH_CTLIN, 0, "fff",
(float) data2, // value
(float) data1, // cc number
(float) channel);
break;
}
case 0xC0: { // program change
_context->sendMessageToReceiverV(HV_HASH_PGMIN, 0, "ff",
(float) data1,
(float) channel);
break;
}
case 0xD0: { // aftertouch
_context->sendMessageToReceiverV(HV_HASH_TOUCHIN, 0, "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, 0, "ff",
(float) value,
(float) channel);
break;
}
default: break;
}
}
#endif


#if DISTRHO_PLUGIN_WANT_MIDI_INPUT
void {{class_name}}::run(const float** inputs, float** outputs, uint32_t frames, const MidiEvent* midiEvents, uint32_t midiEventCount)
{
uint32_t framesDone = 0;
uint32_t curEventIndex = 0;
for (uint32_t i=0; i < midiEventCount; ++i)
{
handleMidiInput(i, midiEvents);
}
#else
void {{class_name}}::run(const float** inputs, float** outputs, uint32_t frames)
{
#endif
```

## Handling MIDI Output

_context->process((float**)inputs, outputs, frames);
For MIDI output you will need to set a heavy sendhook function that will trigger `DPF` MIDI output events from the heavy context:

while (framesDone < frames)
```cpp
static void hvSendHookFunc(HeavyContextInterface *c, const char *sendName, uint32_t sendHash, const HvMessage *m)
{
{{class_name}}* plugin = ({{class_name}}*)c->getUserData();
if (plugin != nullptr)
{
while (curEventIndex < midiEventCount && framesDone == midiEvents[curEventIndex].frame)
#if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
plugin->handleMidiSend(sendHash, m);
#endif
}
}
```
That can then be attached to the heavy context in the constructor:
```cpp
_context->setUserData(this);
_context->setSendHook(&hvSendHookFunc);
```

This will prepare the DPF MidiEvents and needs to take special care for Note Off messages.

Pd does not have specific Note Off events, so velocity 0 is assumed to be Note Off in this case.

Bend assumes input and output values ranged `0 - 16383`.

```cpp
#if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
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
{
if (midiEvents[curEventIndex].size > MidiEvent::kDataSize)
continue;

int status = midiEvents[curEventIndex].data[0];
int command = status & 0xF0;
int channel = status & 0x0F;
int data1 = midiEvents[curEventIndex].data[1];
int data2 = midiEvents[curEventIndex].data[2];

switch (command) {
case 0x80: // note off
case 0x90: { // note on
_context->sendMessageToReceiverV(0x67E37CA3, // __hv_notein
1000.0*frames/getSampleRate(), "fff",
(float) data1, // pitch
(float) data2, // velocity
(float) channel);
break;
}
case 0xB0: { // control change
_context->sendMessageToReceiverV(0x41BE0F9C, // __hv_ctlin
1000.0*frames/getSampleRate(), "fff",
(float) data2, // value
(float) data1, // controller number
(float) channel);
break;
}
case 0xC0: { // program change
_context->sendMessageToReceiverV(0x2E1EA03D, // __hv_pgmin,
1000.0*frames/getSampleRate(), "ff",
(float) data1,
(float) channel);
break;
}
case 0xD0: { // aftertouch
_context->sendMessageToReceiverV(0x553925BD, // __hv_touchin
1000.0*frames/getSampleRate(), "ff",
(float) data1,
(float) channel);
break;
}
case 0xE0: { // pitch bend
hv_uint32_t value = (((hv_uint32_t) data2) << 7) | ((hv_uint32_t) data1);
_context->sendMessageToReceiverV(0x3083F0F7, // __hv_bendin
1000.0*frames/getSampleRate(), "ff",
(float) value,
(float) channel);
break;
}
default: break;
uint8_t note = hv_msg_getFloat(m, 0);
uint8_t velocity = hv_msg_getFloat(m, 1);
uint8_t ch = hv_msg_getFloat(m, 2);

midiSendEvent.size = 3;
if (velocity > 0){
midiSendEvent.data[0] = 0x90 | ch; // Note On
} else {
midiSendEvent.data[0] = 0x80 | ch; // Note Off
}
curEventIndex++;
midiSendEvent.data[1] = note;
midiSendEvent.data[2] = velocity;
midiSendEvent.data[3] = 0;

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

midiSendEvent.size = 3;
midiSendEvent.data[0] = 0xB0 | ch; // send CC
midiSendEvent.data[1] = cc;
midiSendEvent.data[2] = value;
midiSendEvent.data[3] = 0;

writeMidiEvent(midiSendEvent);
break;
}
framesDone++;
case HV_HASH_PGMOUT:
{
uint8_t pgm = hv_msg_getFloat(m, 0);
uint8_t ch = hv_msg_getFloat(m, 1);

midiSendEvent.size = 2;
midiSendEvent.data[0] = 0xC0 | ch; // send Program Change
midiSendEvent.data[1] = pgm;
midiSendEvent.data[2] = 0;
midiSendEvent.data[3] = 0;

writeMidiEvent(midiSendEvent);
break;
}
case HV_HASH_TOUCHOUT:
{
uint8_t value = hv_msg_getFloat(m, 0);
uint8_t ch = hv_msg_getFloat(m, 1);

midiSendEvent.size = 2;
midiSendEvent.data[0] = 0xD0 | ch; // send Touch
midiSendEvent.data[1] = value;
midiSendEvent.data[2] = 0;
midiSendEvent.data[3] = 0;

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

midiSendEvent.size = 3;
midiSendEvent.data[0] = 0xE0 | ch; // send Bend
midiSendEvent.data[1] = lsb;
midiSendEvent.data[2] = msb;
midiSendEvent.data[3] = 0;

writeMidiEvent(midiSendEvent);
break;
}
default:
break;
}
}
```
#endif
```
10 changes: 10 additions & 0 deletions examples/dpf/dpf_bend.pd
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#N canvas 1069 603 354 149 12;
#X obj 30 25 bendin;
#X obj 155 54 hsl 128 15 0 16383 0 0 empty empty empty -2 -8 0 10 -262144
-1 -1 0 1;
#X obj 121 24 r bend @hv_param 0 16383 8192;
#X obj 30 58 print;
#X obj 121 97 bendout;
#X connect 0 0 3 0;
#X connect 1 0 4 0;
#X connect 2 0 4 0;
16 changes: 16 additions & 0 deletions examples/dpf/dpf_midi_thru.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"dpf": {
"project": true,
"description": "super simple test patch",
"maker": "nobody",
"homepage": "https://wasted.audio/plugin/dpf_example",
"plugin_uri": "lv2://wasted.audio/lv2/dpf_example",
"version": "6, 6, 6",
"license": "WTFPL",
"midi_input": 1,
"midi_output": 1,
"plugin_formats": [
"lv2_dsp"
]
}
}
23 changes: 23 additions & 0 deletions examples/dpf/dpf_midi_thru.pd
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#N canvas 327 394 417 108 12;
#X obj 22 19 notein;
#X obj 22 59 noteout;
#X obj 98 19 ctlin;
#X obj 98 59 ctlout;
#X obj 172 19 pgmin;
#X obj 172 59 pgmout;
#X obj 236 19 touchin;
#X obj 236 59 touchout;
#X obj 318 19 bendin;
#X obj 318 59 bendout;
#X connect 0 0 1 0;
#X connect 0 1 1 1;
#X connect 0 2 1 2;
#X connect 2 0 3 0;
#X connect 2 1 3 1;
#X connect 2 2 3 2;
#X connect 4 0 5 0;
#X connect 4 1 5 1;
#X connect 6 0 7 0;
#X connect 6 1 7 1;
#X connect 8 0 9 0;
#X connect 8 1 9 1;
Loading

0 comments on commit baef1e8

Please sign in to comment.