Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MIDI Registered/Non-registered Parameter Support, default instrument changes #293

Merged
merged 11 commits into from
Sep 22, 2024
44 changes: 35 additions & 9 deletions docs/MIDI.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ commands are simply ignored and the rest of the file will play normally.

## Basics

The Swadge's 8-bit audio synthesizer supports up to 16 MIDI channels, with channel 10 reserved for
percussion (the drum kit).
The Swadge's 8-bit audio synthesizer supports up to 16 MIDI channels, with channels 10 and 11 reserved for
percussion (the drum kit). By default, channel 1 through 9 are configured to use the [MAGFest instruments](#MAGPrograms)
in order.

## Features

* 24-voice polyphony shared between non-percussion channels
* 8 additional voices reserved for percussion only channel (channel 10)
* 8 additional voices reserved for percussion only channel (channels 10/11 by default)
* Pitch bend
* AfterTouch

Expand All @@ -25,12 +26,12 @@ For the list of General MIDI instruments available, see [Wikipedia](https://en.w
Note that the sound produced for these instruments is a wavetable rather than full-length samples, so they sound more like a retro
keyboard than the high-fidelity samples you might hear from a modern soft synthesizer.

#### Drum Kit
The Bank 0 Drum Kit follows the key mapping of the General MIDI program sounds, which can be found
on [Wikipedia](https://en.wikipedia.org/wiki/General_MIDI#Percussion). Note that some drum sounds may be incomplete or
not yet supported.
#### GM-Compatible Drum Kit
The Bank 0 Drum Kit is a set of custom drum sounds that follows the standard General MIDI note-to-drum mappings, which can be found
on [Wikipedia](https://en.wikipedia.org/wiki/General_MIDI#Percussion) and is commonly supported by MIDI devices and software. Note
that some drum sounds may be incomplete or not yet supported. This drum kit is available on MIDI Channel 10.

### MAGFest Instruments (Bank 1)
### MAGFest Instruments (Bank 1) {#MAGPrograms}
These instruments are mainly basic wave shapes, with some extra goodies thrown in.

| Program# | Name |
Expand All @@ -45,8 +46,9 @@ These instruments are mainly basic wave shapes, with some extra goodies thrown i
| 7 | Square Wave Hit |
| 8 | Noise Hit |

#### Drum Kit
#### Donut Swadge Drum Kit
The Bank 1 Drum Kit includes a range of noise-based drum sounds originally featured on the [King Donut Swadge](https://swadge.com/donut/).
By default, this drum kit is available on MIDI Channel 11.

| Note | Note Number | Description |
| -------- | ----------- | ----------------- |
Expand Down Expand Up @@ -74,3 +76,27 @@ fine and coarse adjustment knobs on a real audio device, but some editing softwa
| 73 | Attack Time | 0-127 | Set the length of time (in 10ms increments) it will take for a note to reach its maximum volume after it starts playing. | |
| 75 | Decay Time | 0-127 | Set the length of time (in 10ms increments) it will take for a note to fade from its maximum volume to its sustain volume. | |
| 76 | Sustain Level | 0-127 | Set the volume level the note will reach at the end of the decay time, and will be maintained as long as the note is held on. | |
| 6, 38 | Data Entry | 0-16383 | Set the value of the registered or non-registered parameter that was selected previously using the `Registered Parameter` or `Non-registered Parameter` controllers. | |
| 96 | Data Button Increment | 0 (not used) | Increments the value of the registered or non-registered parameter by one | |
| 97 | Data Button Decrement | 0 (not used) | Decrements the value of the registered or non-registered parameter by one | |
| 98, 99 | Non-registered Parameter | 0-16383 | Selects a non-registered parameter to be changed with the `Data Entry`or `Data Button` controllers. | |
| 101, 100 | Registered Parameter | 0-16383 | Selects a registered parameter value to be changed with the `Data Entry` or `Data Button` controllers. | |


## MIDI Non-registered Parameters

These parameters can be changed by first using the `Non-registered Parameter` controller to select a specific parameter, then
using the `Data Entry` or `Data Button` controllers to actually change the parameter's value.

| MIDI NRPN# | Name | Value Range | Description |
| ---------- | ---------- | --------------- | -------------------------------------------------------------------------- |
| 10 | Percussion | 0 (off), 1 (on) | Sets whether this channel plays a drum kit instead of a pitched instrument |

## MIDI System-Exclusive (SysEx) Parameters

The Swadge does not currently support any SysEx commands of its own, but it does support the following Universal SysEx commands.

| Name | Data (Hex) | Description |
| ---------- | ---------------------------- | ----------- |
| GM Enable | <pre>F0 7E 7F 09 01 F7</pre> | Enables General MIDI Mode. All channels are fully reset, and set to use Bank 0, Program 0 ("Acoustic Grand Piano"), and Channel 10 only set to Percussion mode. |
| GM Disable | <pre>F0 7E 7F 09 00 F7</pre> | Disables General MIDI Mode. All channels are fully reset, and set to use Bank 1. Channels 1 through 9 are set to use Programs 0 through 8, respectively. Channel 10 is set to Percussion mode, and set to Bank 0 (General MIDI Compatible Drum kit). Channel 11 is set to Percussion mode, and set to Bank 1 (Donut Swadge Drum kit). Channels 12 through 16 **are currently** set to Bank 0, Program 0 ("Acoustic Grand Piano"), but **note** that this may change in the future and is not a guarantee. |
5 changes: 5 additions & 0 deletions emulator/src/idf/midi_device.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
#include <stdbool.h>
#include <stdint.h>

// Un-comment to enable printing all received MIDI packets
// #define DEBUG_MIDI_PACKETS

static uint8_t runningStatus = 0;
static struct platform_midi_driver* midiDriver = NULL;

Expand Down Expand Up @@ -66,12 +69,14 @@ bool tud_midi_n_packet_read(uint8_t itf, uint8_t packet[4])
int read = midiDriver ? platform_midi_read(midiDriver, real_packet, sizeof(real_packet)) : 0;
if (read > 0)
{
#ifdef DEBUG_MIDI_PACKETS
printf("Packet: ");
for (int i = 0; i < read; i++)
{
printf("%hhx, ", real_packet[i]);
}
printf("\n");
#endif

// Normally start reading after the status byte
int dataOffset = 1;
Expand Down
54 changes: 31 additions & 23 deletions main/midi/midiFileParser.c
Original file line number Diff line number Diff line change
Expand Up @@ -516,26 +516,6 @@ static bool trackParseNext(midiFileReader_t* reader, midiTrackState_t* track)
// A sysex event in a MIDI file is different from one in streaming mode (i.e. USB), because there is no
// length byte transmitted in streaming mode, so we need to check the manufacturer length there

// TODO: Move this to the streaming parser
/*
// We'll need to read at least one more byte
//if (!TRK_REMAIN()) { ERR(); }
uint16_t manufacturer = *(track->cur++);
if (!manufacturer)
{
if (TRK_REMAIN() < 2) { ERR(); }
// A manufacturer ID of 0 means the ID is actually in the next 2 bytes
manufacturer = *(track->cur++);
manufacturer <<= 7;
manufacturer |= *(track->cur++);
}
else
{
// Technically 0x00 0x00 0x41 is considered a different manufacturer from the single-byte value 0x41
// So in that case just put a 1 in the 15th bit that's otherwise unused
manufacturer |= (1 << 15);
}*/

uint32_t sysexLength;
read = readVariableLength(track->cur, TRK_REMAIN(), &sysexLength);
if (!read)
Expand All @@ -554,10 +534,38 @@ static bool trackParseNext(midiFileReader_t* reader, midiTrackState_t* track)
track->nextEvent.sysex.data = track->cur;
// If the status is 0xF0, the 0xF0 should be prefixed to the data.
track->nextEvent.sysex.prefix = (status == 0xF0) ? 0xF0 : 0x00;
// TODO
track->nextEvent.sysex.manufacturerId = 0;

track->cur += sysexLength;
// Store the pointer to the end of data so we don't need to do math later
const uint8_t* sysexEnd = (track->cur + sysexLength);

// TODO: Move this to the streaming parser
// We'll need to read at least one more byte
if (!TRK_REMAIN())
{
ERR();
}
uint16_t manufacturer = *(track->cur++);
if (!manufacturer)
{
if (TRK_REMAIN() < 2)
{
ERR();
}
// A manufacturer ID of 0 means the ID is actually in the next 2 bytes
manufacturer = *(track->cur++);
manufacturer <<= 7;
manufacturer |= *(track->cur++);
}
else
{
// Technically 0x00 0x00 0x41 is considered a different manufacturer from the single-byte value 0x41
// So in that case just put a 1 in the 15th bit that's otherwise unused
manufacturer |= (1 << 15);
}

track->nextEvent.sysex.manufacturerId = manufacturer;

track->cur = sysexEnd;
}
else
{
Expand Down
Loading
Loading