aboutsummaryrefslogtreecommitdiff
path: root/engines/sci/sfx/seq/instrument-map.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/sci/sfx/seq/instrument-map.cpp')
-rw-r--r--engines/sci/sfx/seq/instrument-map.cpp539
1 files changed, 539 insertions, 0 deletions
diff --git a/engines/sci/sfx/seq/instrument-map.cpp b/engines/sci/sfx/seq/instrument-map.cpp
new file mode 100644
index 0000000000..0d829a0582
--- /dev/null
+++ b/engines/sci/sfx/seq/instrument-map.cpp
@@ -0,0 +1,539 @@
+/***************************************************************************
+ Copyright (C) 2008 Christoph Reichenbach
+
+
+ This program may be modified and copied freely according to the terms of
+ the GNU general public license (GPL), as long as the above copyright
+ notice and the licensing information contained herein are preserved.
+
+ Please refer to www.gnu.org for licensing details.
+
+ This work is provided AS IS, without warranty of any kind, expressed or
+ implied, including but not limited to the warranties of merchantability,
+ noninfringement, and fitness for a specific purpose. The author will not
+ be held liable for any damage caused by this work or derivatives of it.
+
+ By using this source code, you agree to the licensing terms as stated
+ above.
+
+
+ Please contact the maintainer for bug reports or inquiries.
+
+ Current Maintainer:
+
+ Christoph Reichenbach (CR) <creichen@gmail.com>
+
+***************************************************************************/
+
+#include <assert.h>
+#include "sci_midi.h"
+#include "sci_memory.h"
+#include "instrument-map.h"
+#include "sfx_engine.h"
+
+sfx_instrument_map_t *
+sfx_instrument_map_new(int velocity_maps_nr)
+{
+ sfx_instrument_map_t *map = (sfx_instrument_map_t *)sci_malloc(sizeof (sfx_instrument_map_t));
+ int i;
+
+ map->initialisation_block_size = 0;
+ map->initialisation_block = NULL;
+
+ map->velocity_maps_nr = velocity_maps_nr;
+ map->percussion_velocity_map_index = SFX_NO_VELOCITY_MAP;
+
+ if (velocity_maps_nr == 0)
+ map->velocity_map = NULL; /* Yes, this complicates control flow needlessly, but it avoids some of the pointless
+ ** warnings that certain memory tools seem to find appropriate. */
+ else {
+ map->velocity_map = (byte **)sci_malloc(sizeof (byte *) * velocity_maps_nr);
+ for (i = 0; i < velocity_maps_nr; ++i)
+ map->velocity_map[i] = (byte *)sci_malloc(SFX_VELOCITIES_NR);
+ }
+ for (i = 0; i < SFX_INSTRUMENTS_NR; ++i)
+ map->velocity_map_index[i] = SFX_NO_VELOCITY_MAP;
+
+ map->percussion_volume_adjust = 0;
+ for (i = 0; i < SFX_RHYTHM_NR; ++i)
+ map->percussion_map[i] = i;
+
+
+ for (i = 0; i < SFX_INSTRUMENTS_NR; ++i) {
+ map->patch_map[i].patch = i;
+ map->patch_key_shift[i] = 0;
+ map->patch_volume_adjust[i] = 0;
+ }
+
+ return map;
+}
+
+void
+sfx_instrument_map_free(sfx_instrument_map_t *map)
+{
+ if (!map)
+ return;
+
+ if (map->velocity_map) {
+ int i;
+ for (i = 0; i < map->velocity_maps_nr; i++)
+ sci_free(map->velocity_map[i]);
+ sci_free(map->velocity_map);
+ map->velocity_map = NULL;
+ }
+
+ if (map->initialisation_block) {
+ sci_free(map->initialisation_block);
+ map->initialisation_block = NULL;
+ }
+
+ sci_free(map);
+}
+
+#define PATCH_MAP_OFFSET 0x0000
+#define PATCH_KEY_SHIFT_OFFSET 0x0080
+#define PATCH_VOLUME_ADJUST_OFFSET 0x0100
+#define PATCH_PERCUSSION_MAP_OFFSET 0x0180
+#define PATCH_PERCUSSION_VOLUME_ADJUST 0x0200
+#define PATCH_VELOCITY_MAP_INDEX 0x0201
+#define PATCH_VELOCITY_MAP(i) (0x0281 + (0x80 * i))
+#define PATCH_INIT_DATA_SIZE_LE 0x0481
+#define PATCH_INIT_DATA 0x0483
+
+#define PATCH_INSTRUMENT_MAPS_NR 4
+
+#define PATCH_MIN_SIZE PATCH_INIT_DATA
+
+
+static int
+patch001_type0_length(byte *data, size_t length)
+{
+ unsigned int pos = 492 + 246 * data[491];
+
+/* printf("timbres %d (post = %04x)\n",data[491], pos);*/
+
+ if ((length >= (pos + 386)) && (data[pos] == 0xAB) && (data[pos + 1] == 0xCD))
+ pos += 386;
+
+/* printf("pos = %04x (%02x %02x)\n", pos, data[pos], data[pos + 1]); */
+
+ if ((length >= (pos + 267)) && (data[pos] == 0xDC) && (data[pos + 1] == 0xBA))
+ pos += 267;
+
+/* printf("pos = %04x %04x (%d)\n", pos, length, pos-length); */
+
+
+ if (pos == length)
+ return 1;
+ return 0;
+}
+
+static int
+patch001_type1_length(byte *data, size_t length)
+{
+ if ((length >= 1155) && (((data[1154] << 8) + data[1153] + 1155) == length))
+ return 1;
+ return 0;
+}
+
+int
+sfx_instrument_map_detect(byte *data, size_t length)
+{
+ /* length test */
+ if (length < 1155)
+ return SFX_MAP_MT32;
+ if (length > 16889)
+ return SFX_MAP_MT32_GM;
+ if (patch001_type0_length(data, length) &&
+ !patch001_type1_length(data, length))
+ return SFX_MAP_MT32;
+ if (patch001_type1_length(data, length) &&
+ !patch001_type0_length(data, length))
+ return SFX_MAP_MT32_GM;
+ return SFX_MAP_UNKNOWN;
+}
+
+
+sfx_instrument_map_t *
+sfx_instrument_map_load_sci(byte *data, size_t size)
+{
+ sfx_instrument_map_t * map;
+ int i, m;
+
+ if (data == NULL)
+ return NULL;
+
+ if (size < PATCH_MIN_SIZE) {
+ fprintf(stderr, "[instrument-map] Instrument map too small: %d of %d\n", (int) size, PATCH_MIN_SIZE);
+ return NULL;
+ }
+
+ map = sfx_instrument_map_new(PATCH_INSTRUMENT_MAPS_NR);
+
+ /* Set up MIDI intialisation data */
+ map->initialisation_block_size = getInt16(data + PATCH_INIT_DATA_SIZE_LE);
+ if (map->initialisation_block_size) {
+ if (size < PATCH_MIN_SIZE + map->initialisation_block_size) {
+ fprintf(stderr, "[instrument-map] Instrument map too small for initialisation block: %d of %d\n", (int) size, PATCH_MIN_SIZE);
+ return NULL;
+ }
+
+ if (size > PATCH_MIN_SIZE + map->initialisation_block_size)
+ fprintf(stderr, "[instrument-map] Instrument larger than required by initialisation block: %d of %d\n", (int) size, PATCH_MIN_SIZE);
+
+ if (map->initialisation_block_size != 0) {
+ map->initialisation_block = (byte *)sci_malloc(map->initialisation_block_size);
+ memcpy(map->initialisation_block, data + PATCH_INIT_DATA, map->initialisation_block_size);
+ }
+ }
+
+ /* Set up basic instrument info */
+ for (i = 0; i < SFX_INSTRUMENTS_NR; i++) {
+ map->patch_map[i].patch = (char)data[PATCH_MAP_OFFSET + i];
+ map->patch_key_shift[i] = (char)data[PATCH_KEY_SHIFT_OFFSET + i];
+ map->patch_volume_adjust[i] = (char)data[PATCH_VOLUME_ADJUST_OFFSET + i];
+ map->patch_bend_range[i] = SFX_UNMAPPED;
+ map->velocity_map_index[i] = data[PATCH_VELOCITY_MAP_INDEX + i];
+ }
+
+ /* Set up percussion maps */
+ map->percussion_volume_adjust = data[PATCH_PERCUSSION_VOLUME_ADJUST];
+ for (i = 0; i < SFX_RHYTHM_NR; i++) {
+ map->percussion_map[i] = data[PATCH_PERCUSSION_MAP_OFFSET + i];
+ map->percussion_velocity_scale[i] = SFX_MAX_VELOCITY;
+ }
+
+ /* Set up velocity maps */
+ for (m = 0; m < PATCH_INSTRUMENT_MAPS_NR; m++) {
+ byte *velocity_map = map->velocity_map[m];
+ for (i = 0; i < SFX_VELOCITIES_NR; i++)
+ velocity_map[i] = data[PATCH_VELOCITY_MAP(m) + i];
+ }
+
+ map->percussion_velocity_map_index = 0;
+
+ return map;
+}
+
+
+/* Output with the instrument map */
+#define MIDI_CHANNELS_NR 0x10
+
+typedef struct decorated_midi_writer {
+ MIDI_WRITER_BODY
+
+ midi_writer_t *writer;
+ sfx_patch_map_t patches[MIDI_CHANNELS_NR];
+ sfx_instrument_map_t *map;
+} decorated_midi_writer_t;
+
+
+static void
+init_decorated(struct _midi_writer *self_)
+{
+ decorated_midi_writer_t *self = (decorated_midi_writer_t *) self_;
+ self->writer->init(self->writer);
+}
+
+static void
+set_option_decorated(struct _midi_writer *self_, char *name, char *value)
+{
+ decorated_midi_writer_t *self = (decorated_midi_writer_t *) self_;
+ self->writer->set_option(self->writer, name, value);
+}
+
+static void
+delay_decorated(struct _midi_writer *self_, int ticks)
+{
+ decorated_midi_writer_t *self = (decorated_midi_writer_t *) self_;
+ self->writer->delay(self->writer, ticks);
+}
+
+static void
+flush_decorated(struct _midi_writer *self_)
+{
+ decorated_midi_writer_t *self = (decorated_midi_writer_t *) self_;
+ if (self->writer->flush)
+ self->writer->flush(self->writer);
+}
+
+static void
+reset_timer_decorated(struct _midi_writer *self_)
+{
+ decorated_midi_writer_t *self = (decorated_midi_writer_t *) self_;
+ self->writer->reset_timer(self->writer);
+}
+
+
+static void
+close_decorated(decorated_midi_writer_t *self)
+{
+ sfx_instrument_map_free(self->map);
+ self->map = NULL;
+ self->writer->close(self->writer);
+ sci_free(self->name);
+ self->name = NULL;
+ sci_free(self);
+}
+
+#define BOUND_127(x) (((x) < 0)? 0 : (((x) > 0x7f)? 0x7f : (x)))
+
+static int
+bound_hard_127(int i, char *descr)
+{
+ int r = BOUND_127(i);
+ if (r != i)
+ fprintf(stderr, "[instrument-map] Hard-clipping %02x to %02x in %s\n", i, r, descr);
+ return r;
+}
+
+static int
+set_bend_range(midi_writer_t *writer, int channel, int range)
+{
+ byte buf[3] = {0xb0, 0x65, 0x00};
+
+ buf[0] |= channel & 0xf;
+ if (writer->write(writer, buf, 3) != SFX_OK)
+ return SFX_ERROR;
+
+ buf[1] = 0x64;
+ if (writer->write(writer, buf, 3) != SFX_OK)
+ return SFX_ERROR;
+
+ buf[1] = 0x06;
+ buf[2] = BOUND_127(range);
+ if (writer->write(writer, buf, 3) != SFX_OK)
+ return SFX_ERROR;
+
+ buf[1] = 0x26;
+ buf[2] = 0;
+ if (writer->write(writer, buf, 3) != SFX_OK)
+ return SFX_ERROR;
+
+ return SFX_OK;
+}
+
+static int
+write_decorated(decorated_midi_writer_t *self, byte *buf, int len)
+{
+ sfx_instrument_map_t *map = self->map;
+ int op = *buf & 0xf0;
+ int chan = *buf & 0x0f;
+ int patch = self->patches[chan].patch;
+ int rhythm = self->patches[chan].rhythm;
+
+ assert (len >= 1);
+
+ if (op == 0xC0 && chan != MIDI_RHYTHM_CHANNEL) { /* Program change */
+ int patch = bound_hard_127(buf[1], "program change");
+ int instrument = map->patch_map[patch].patch;
+ int bend_range = map->patch_bend_range[patch];
+
+ self->patches[chan] = map->patch_map[patch];
+
+ if (instrument == SFX_UNMAPPED || instrument == SFX_MAPPED_TO_RHYTHM)
+ return SFX_OK;
+
+ assert (len >= 2);
+ buf[1] = bound_hard_127(instrument, "patch lookup");
+
+ if (self->writer->write(self->writer, buf, len) != SFX_OK)
+ return SFX_ERROR;
+
+ if (bend_range != SFX_UNMAPPED)
+ return set_bend_range(self->writer, chan, bend_range);
+
+ return SFX_OK;
+ }
+
+ if (chan == MIDI_RHYTHM_CHANNEL || patch == SFX_MAPPED_TO_RHYTHM) {
+ /* Rhythm channel handling */
+ switch (op) {
+ case 0x80:
+ case 0x90: { /* Note off / note on */
+ int velocity, instrument, velocity_map_index, velocity_scale;
+
+ if (patch == SFX_MAPPED_TO_RHYTHM) {
+ buf[0] = (buf[0] & ~0x0f) | MIDI_RHYTHM_CHANNEL;
+ instrument = rhythm;
+ velocity_scale = SFX_MAX_VELOCITY;
+ } else {
+ int instrument_index = bound_hard_127(buf[1], "rhythm instrument index");
+ instrument = map->percussion_map[instrument_index];
+ velocity_scale = map->percussion_velocity_scale[instrument_index];
+ }
+
+ if (instrument == SFX_UNMAPPED)
+ return SFX_OK;
+
+ assert (len >= 3);
+
+ velocity = bound_hard_127(buf[2], "rhythm velocity");
+ velocity_map_index = map->percussion_velocity_map_index;
+
+ if (velocity_map_index != SFX_NO_VELOCITY_MAP)
+ velocity = BOUND_127(velocity + map->velocity_map[velocity_map_index][velocity]);
+
+ velocity = BOUND_127(velocity * velocity_scale / SFX_MAX_VELOCITY);
+
+ buf[1] = bound_hard_127(instrument, "rhythm instrument");
+ buf[2] = velocity;
+
+ break;
+ }
+
+ case 0xB0: { /* Controller change */
+ assert (len >= 3);
+ if (buf[1] == 0x7) /* Volume change */
+ buf[2] = BOUND_127(buf[2] + map->percussion_volume_adjust);
+ break;
+ }
+
+ default: break;
+ }
+
+ } else {
+ /* Instrument channel handling */
+
+ if (patch == SFX_UNMAPPED)
+ return SFX_OK;
+
+ switch (op) {
+ case 0x80:
+ case 0x90: { /* Note off / note on */
+ int note = bound_hard_127(buf[1], "note");
+ int velocity = bound_hard_127(buf[2], "velocity");
+ int velocity_map_index = map->velocity_map_index[patch];
+ assert (len >= 3);
+
+ note += map->patch_key_shift[patch];
+ /* Not the most efficient solutions, but the least error-prone */
+ while (note < 0)
+ note += 12;
+ while (note > 0x7f)
+ note -= 12;
+
+ if (velocity_map_index != SFX_NO_VELOCITY_MAP)
+ velocity = BOUND_127(velocity + map->velocity_map[velocity_map_index][velocity]);
+
+ buf[1] = note;
+ buf[2] = velocity;
+ break;
+ }
+
+ case 0xB0: /* Controller change */
+ assert (len >= 3);
+ if (buf[1] == 0x7) /* Volume change */
+ buf[2] = BOUND_127(buf[2] + map->patch_volume_adjust[patch]);
+ break;
+
+ default: break;
+ }
+ }
+
+ return self->writer->write(self->writer, buf, len);
+}
+
+#define MIDI_BYTES_PER_SECOND 3250 /* This seems to be the minimum guarantee by the standard */
+#define MAX_PER_TICK (MIDI_BYTES_PER_SECOND / 60) /* After this, we ought to issue one tick of pause */
+
+static void
+init(midi_writer_t *writer, byte *data, size_t len)
+{
+ int offset = 0;
+ byte status = 0;
+
+ /* Send init data as separate MIDI commands */
+ while (offset < len) {
+ int args;
+ byte op = data[offset];
+ byte msg[3];
+ int i;
+
+ if (op == 0xf0) {
+ int msg_len;
+ byte *find = (byte *) memchr(data + offset, 0xf7, len - offset);
+
+ if (!find) {
+ fprintf(stderr, "[instrument-map] Failed to find end of sysex message\n");
+ return;
+ }
+
+ msg_len = find - data - offset + 1;
+ writer->write(writer, data + offset, msg_len);
+
+ /* Wait at least 40ms after sysex */
+ writer->delay(writer, 3);
+ offset += msg_len;
+ continue;
+ }
+
+ if (op < 0x80)
+ op = status;
+ else {
+ status = op;
+ offset++;
+ }
+
+ msg[0] = op;
+
+ switch (op & 0xf0) {
+ case 0xc0:
+ case 0xd0:
+ args = 1;
+ break;
+ default:
+ args = 2;
+ }
+
+ if (args > len - offset) {
+ fprintf(stderr, "[instrument-map] Insufficient bytes remaining for MIDI command %02x\n", op);
+ return;
+ }
+
+ for (i = 0; i < args; i++)
+ msg[i + 1] = data[offset + i];
+
+ writer->write(writer, msg, args + 1);
+ offset += args;
+
+ if (writer->flush)
+ writer->flush(writer);
+ }
+}
+
+#define NAME_SUFFIX "+instruments"
+
+midi_writer_t *
+sfx_mapped_writer(midi_writer_t *writer, sfx_instrument_map_t *map)
+{
+ int i;
+ decorated_midi_writer_t *retval;
+
+ if (map == NULL)
+ return writer;
+
+ retval = (decorated_midi_writer_t *)sci_malloc(sizeof(decorated_midi_writer_t));
+ retval->writer = writer;
+ retval->name = (char *)sci_malloc(strlen(writer->name) + strlen(NAME_SUFFIX) + 1);
+ strcpy(retval->name, writer->name);
+ strcat(retval->name, NAME_SUFFIX);
+
+ retval->init = (int (*)(midi_writer_t *)) init_decorated;
+ retval->set_option = (int (*)(midi_writer_t *, char *, char *)) set_option_decorated;
+ retval->write = (int (*)(midi_writer_t *, byte *, int)) write_decorated;
+ retval->delay = (void (*)(midi_writer_t *, int)) delay_decorated;
+ retval->flush = (void (*)(midi_writer_t *)) flush_decorated;
+ retval->reset_timer = (void (*)(midi_writer_t *)) reset_timer_decorated;
+ retval->close = (void (*)(midi_writer_t *)) close_decorated;
+
+ retval->map = map;
+
+ init(writer, map->initialisation_block, map->initialisation_block_size);
+
+ for (i = 0; i < MIDI_CHANNELS_NR; i++)
+ retval->patches[i].patch = SFX_UNMAPPED;
+
+ return (midi_writer_t *) retval;
+}
+