Program Listing for File dreammakerfx.cpp

Return to documentation for file (src/dreammakerfx.cpp)

// Copyright (c) 2020 Run Jump Labs LLC.  All right reserved.
// This code is licensed under MIT license (see license.txt for details)
#include <stdlib.h>

#include "Arduino.h"
#include "dreammakerfx.h"


fx_pedal pedal;

/************************************************************************
 *  Initialization
 ***********************************************************************/

void fx_pedal::init(void) {
  init(MSG_WARN, false);
}


void fx_pedal::init(DEBUG_MSG_LEVEL debug_level) {
  init(debug_level, false);
}


[[deprecated("Replaced by init(DEBUG_MSG_LEVEL debug_level), which has an improved interface")]]
void fx_pedal::init(bool debug_enable) {
  if (debug_enable) {
    init(MSG_INFO);
  } else {
    init();
  }
}

[[deprecated("Replaced by init(DEBUG_MSG_LEVEL debug_level), which has an improved interface")]]
void fx_pedal::init(bool debug_enable, bool dsp_telem) {
  if (dsp_telem) {
    init(MSG_DEBUG);
  } else {

  }
}

void fx_pedal::init(DEBUG_MSG_LEVEL debug_level, bool dsp_no_reset) {

  dmfx_debug_level = debug_level;
  dmfx_debug_no_reset = debug_no_reset = dsp_no_reset;

  #if defined (DM_FX)
    // Setup LEDs
    pinMode(PIN_FOOTSW_LED_1, OUTPUT);
    pinMode(PIN_FOOTSW_LED_2, OUTPUT);
    pinMode(PIN_ARD_LED, OUTPUT);
    //pinMode(PIN_USR_PB, INPUT);
    //attachInterrupt(digitalPinToInterrupt(PIN_USR_PB), user_pb_pressed, FALLING);

  #elif defined (DM_FX_TWO)
    pinMode(PIN_ARD_LED_G, OUTPUT);
    pinMode(PIN_ARD_LED_Y, OUTPUT);
    digitalWrite(PIN_ARD_LED_G, HIGH);
    digitalWrite(PIN_ARD_LED_Y, LOW);
  #endif

  // Initialize hardware pins
  pinMode(PIN_FOOTSW_1, INPUT);
  pinMode(PIN_FOOTSW_2, INPUT);

  bool left = digitalRead(PIN_FOOTSW_LEFT);
  bool right = digitalRead(PIN_FOOTSW_RIGHT);

  // if both buttons held, reboot into bootloader
  if (!left && ! right) {
    for (int i=0;i<10;i++) {
      turn_on_left_footsw_led();
      turn_off_right_footsw_led();;
      delay(100);
      turn_off_left_footsw_led();;
      turn_on_right_footsw_led();;
      delay(100);
    }
    reset_into_bootloader();
  }

  // Initialize the RGB LEDs if present

  rgb_leds_init();
  turn_on_left_footsw_led_rgb(0, 0, 200);
  turn_on_center_footsw_led_rgb(0, 0, 200);
  turn_on_right_footsw_led_rgb(0, 0, 200);

  int now = millis();
  bool timeout = false;
  while ( !Serial && !timeout) {
    if (millis() > now + 2000) {
      timeout = true;
    }
  }

    // Set up telemetry link to SHARC
  Serial1.begin(115200);
  // Set up Serial USB link
  Serial.begin(115200);
  Serial.print("DreamMaker FX By Run Jump Labs");
  Serial.print(" (version: ");
  String package_str = String(API_VERSION);
  package_str.replace("0",".");
  Serial.print(package_str);
  Serial.println(")");

  #if defined (DM_FX)
    // Initialize the WM8731
    wm8731_initialize();
  #elif defined (DM_FX_TWO)
    // Initialize SigmaDSP
    adau1761_initialize();
  #endif


  turn_on_left_footsw_led_rgb(100, 0, 100);
  turn_on_center_footsw_led_rgb(100, 0, 100);
  turn_on_right_footsw_led_rgb(100, 0, 100);


  attachInterrupt(digitalPinToInterrupt(PIN_FOOTSW_1), footswitch_right_pressed_isr, FALLING);
  attachInterrupt(digitalPinToInterrupt(PIN_FOOTSW_2), footswitch_left_pressed_isr, FALLING);

  if (!dsp_no_reset) {
    // Reset the DSP
    DEBUG_MSG("Resetting DSP", MSG_DEBUG);
    dsp_reset();
  } else {
    // If we're not resetting DSP, at least make sure it's not still using the SPI port to boot
    DEBUG_MSG("Bypassing DSP reset", MSG_DEBUG);
    wait_for_dsp_spi_flash_access_to_cease();
  }

  turn_on_left_footsw_led_rgb(100, 100, 0);
  turn_on_center_footsw_led_rgb(100, 100, 0);
  turn_on_right_footsw_led_rgb(100, 100, 0);








  // Wait for the DSP to boot and report firmware
  wait_for_dsp_to_boot();

  turn_on_left_footsw_led_rgb(100, 100, 100);
  turn_on_center_footsw_led_rgb(100, 100, 100);
  turn_on_right_footsw_led_rgb(100, 100, 100);

  bool firmware_match = true;
  if (dsp_status.firmware_ver != API_VERSION) {
    firmware_match = false;
  }
  if (!firmware_match) {
    Serial.println(" The Arduino package version does not match the DSP firmware version, updating firmware...");
    dsp_update_firmware_image();
    dsp_reset();
    wait_for_dsp_spi_flash_access_to_cease();
    wait_for_dsp_to_boot();
  }

  // Wait for the DSP to be ready
  wait_for_dsp_to_be_ready();

  turn_off_left_footsw_led();
  turn_off_center_footsw_led();
  turn_off_right_footsw_led();

  initialized = true;


}




/******************************************************************************
 *  DSP SPI Protocol
 *****************************************************************************/

#ifndef DOXYGEN_SHOULD_SKIP_THIS

void  fx_pedal::spi_transmit_control_routing_stack(void) {

  DEBUG_MSG("Starting", MSG_DEBUG);

  uint16_t * routing_block = (uint16_t *) malloc(total_control_routes*8*sizeof(uint16_t));
  if (routing_block == NULL) {
      DEBUG_MSG("Could not allocate space for routing block", MSG_ERROR);
      display_error_status(ERROR_INTERNAL);
  }

  // serialize routing data
  int indx = 0;
  routing_block[indx++] = HEADER_CONTROL_ROUTING_BLOCK;
  for (int i=0;i<total_control_routes;i++) {
    routing_block[indx++] = (control_routing_stack[i].src_id << 8) | control_routing_stack[i].src_node_indx;
    routing_block[indx++] = (control_routing_stack[i].dest_id << 8) | control_routing_stack[i].dest_node_indx;
    routing_block[indx++] = control_routing_stack[i].src_param_id;
    routing_block[indx++] = control_routing_stack[i].dest_param_id;
    float scale = control_routing_stack[i].scale;
    float offset = control_routing_stack[i].offset;
    uint32_t part_32 = * (uint32_t *) &scale;
    routing_block[indx++] = (uint16_t) (part_32 >> 16);
    routing_block[indx++] = (uint16_t) (part_32 & 0xFFFF);
    part_32 = * (uint32_t *) &offset;
    routing_block[indx++] = (uint16_t) (part_32 >> 16);
    routing_block[indx++] = (uint16_t) (part_32 & 0xFFFF);
    routing_block[indx++] = (uint16_t) control_routing_stack[i].type;

  }

  // Copy to SPI transmit fifo
  spi_fifo_insert_block(routing_block, indx);

  free (routing_block);

  DEBUG_MSG("Complete", MSG_DEBUG);

}

void  fx_pedal::spi_transmit_audio_routing_stack(void) {

  DEBUG_MSG("Starting", MSG_DEBUG);

  uint16_t * routing_block = (uint16_t *) malloc(total_audio_routes*2*sizeof(uint16_t));
  if (routing_block == NULL) {
      DEBUG_MSG("Could not allocate space for routing block", MSG_ERROR);
      display_error_status(ERROR_INTERNAL);
  }

  // serialize routing data
  int indx = 0;
  routing_block[indx++] = HEADER_AUDIO_ROUTING_BLOCK;
  for (int i=0;i<total_audio_routes;i++) {
    routing_block[indx++] = (audio_routing_stack[i].src_id << 8) | audio_routing_stack[i].src_node_indx;
    routing_block[indx++] = (audio_routing_stack[i].dest_id << 8) | audio_routing_stack[i].dest_node_indx;
  }

  // Copy to SPI transmit fifo
  spi_fifo_insert_block(routing_block, indx);

  DEBUG_MSG("Complete", MSG_DEBUG);


}


void fx_pedal::spi_transmit_instance_stack(void) {

  DEBUG_MSG("Starting", MSG_DEBUG);

  uint16_t * spi_instance_block = (uint16_t *) malloc((total_instances + 1)*sizeof(uint16_t));
  if (spi_instance_block == NULL) {
      DEBUG_MSG("Could not allocate space for routing block", MSG_ERROR);
      display_error_status(ERROR_INTERNAL);
  }

  // serialize instance data
  int indx = 0;
  spi_instance_block[indx++] = HEADER_INSTANCE_BLOCK;
  for (int i=0;i<total_instances;i++) {
    spi_instance_block[indx++] = (instance_stack[i].type << 8) | instance_stack[i].id;
  }

  // Copy to SPI transmit fifo
  spi_fifo_insert_block(spi_instance_block, indx);

  free(spi_instance_block);

  DEBUG_MSG("Complete", MSG_DEBUG);

}

void fx_pedal::spi_transmit_param(EFFECT_TYPE instance_type, uint32_t instance_id, PARAM_TYPES param_type, uint8_t param_id, void * value) {

  DEBUG_MSG("Starting", MSG_DEBUG);

  static uint16_t param_block[7] = {HEADER_SINGLE_PARAMETER};

  uint16_t part_16;
  uint32_t part_32;

  param_block[1] = (uint16_t) instance_type;
  param_block[2] = (uint16_t) instance_id;
  param_block[3] = (uint16_t) param_type;
  param_block[4] = param_id;

  if (param_type == T_BOOL) {
    part_16 = * (uint8_t *) value;
    param_block[5] = part_16;
  }
  else if (param_type == T_INT16) {
    param_block[5] = (uint16_t) (* (uint16_t *) value);
  }
  else if (param_type == T_INT32) {
    param_block[5] = (uint16_t)((* (uint32_t *) value) >> 16);
    param_block[6] = (uint16_t)((* (uint32_t *) value) & 0xFFFF);
  }
  else if (param_type == T_FLOAT) {
    part_32 = * (uint32_t *) value;
    param_block[5] = (uint16_t) (part_32 >> 16);
    param_block[6] = (uint16_t) (part_32 & 0xFFFF);
  }

  // Add to transmit FIFO
  spi_fifo_insert_block(param_block, sizeof(param_block)/sizeof(uint16_t));

  DEBUG_MSG("Complete", MSG_DEBUG);

}

void fx_pedal::spi_transmit_bypass(uint16_t bypass_state) {

  DEBUG_MSG("Starting", MSG_DEBUG);
  uint16_t param_block[2];

  param_block[0] = HEADER_SET_BYPASS;
  param_block[1] = bypass_state;

  // Copy to SPI transmit fifo
  spi_fifo_insert_block(param_block, 2);

  DEBUG_MSG("Complete", MSG_DEBUG);

}

void fx_pedal::spi_get_status(void) {

  uint16_t param_block[1];
  param_block[0] = HEADER_GET_STATUS;

  spi_fifo_insert_block(param_block, SPI_DSP_STAT_FRAME_SIZE);

}

void fx_pedal::spi_transmit_params(uint16_t node_index) {

  DEBUG_MSG("Starting", MSG_DEBUG);

  uint16_t param_block[MAX_PARMS_PER_FX];
  uint16_t size;

  if (!node_index) {
    DEBUG_MSG("This instance is not part of a canvas", MSG_ERROR);
    display_error_status(ERROR_CODE_ILLEGAL_ROUTING);
  }

  fx_effect * effect = (fx_effect *) instance_stack[node_index].address;

  if (effect == NULL) {
    DEBUG_MSG("NULL effect encountered", MSG_ERROR);
    display_error_status(ERROR_CODE_ILLEGAL_ROUTING);
  }

  #if 0
    Serial.println("Generating parameter transmit block");
  #endif

  size = 0;
  param_block[0] = HEADER_PARAMETER_BLOCK;
  param_block[1] = (uint16_t) instance_stack[node_index].type;  // instance type
  param_block[2] = (uint16_t) instance_stack[node_index].id;    // instance id
  effect->serialize_params(&param_block[3], &size);
  size += 3;

  // Copy to SPI transmit fifo
  spi_fifo_insert_block(param_block, size);

  DEBUG_MSG("Complete", MSG_DEBUG);

}

void fx_pedal::spi_transmit_all_params(void) {

  DEBUG_MSG("Starting", MSG_DEBUG);

  uint16_t param_block[MAX_PARMS_PER_FX];
  uint16_t size;

  param_block[0] = HEADER_PARAMETER_BLOCK;

  for (int i=1;i<total_instances;i++) {
    size = 0;

    fx_effect * effect = (fx_effect *) instance_stack[i].address;

    if (effect == NULL) {
      DEBUG_MSG("NULL effect encountered", MSG_ERROR);
      display_error_status(ERROR_CODE_ILLEGAL_ROUTING);
    } else {
      param_block[1] = (uint16_t) instance_stack[i].type;  // instance type
      param_block[2] = (uint16_t) instance_stack[i].id;    // instance id
      effect->serialize_params(&param_block[3], &size);
      size += 3;

      // Copy to SPI transmit block
      spi_fifo_insert_block(param_block, size);

      if (dmfx_debug_level == MSG_DEBUG) {
        Serial.print("  Type: ");
        Serial.print(instance_stack[i].type);
        Serial.print(", ID: ");
        Serial.print(instance_stack[i].id);
        Serial.print(", Size: ");
        Serial.println(size);
        #if 0
          for (int i=0;i<size;i++) {
            Serial.print("    0x");
            Serial.println(param_block[i], HEX);
          }
        #endif
      }

    }
  }

  DEBUG_MSG("Complete", MSG_DEBUG);
}




void  fx_pedal::spi_service(void) {
  spi_transmit_buffered_frames(false);
}

#endif // DOXYGEN_SHOULD_SKIP_THIS








/******************************************************************************
 *  Audio and control routing
 *****************************************************************************/


bool  fx_pedal::add_audio_route_to_stack(uint8_t src_id,
                                          uint8_t src_node_indx,
                                          uint8_t dest_id,
                                          uint8_t dest_node_indx) {
  audio_routing_stack[total_audio_routes].src_id = src_id;
  audio_routing_stack[total_audio_routes].src_node_indx = src_node_indx;
  audio_routing_stack[total_audio_routes].dest_id = dest_id;
  audio_routing_stack[total_audio_routes].dest_node_indx = dest_node_indx;
  total_audio_routes++;

  return true;
}

bool  fx_pedal::add_control_route_to_stack(uint8_t src_id,
                                            uint8_t src_node_indx,
                                            uint8_t src_param_id,
                                            uint8_t dest_id,
                                            uint8_t dest_node_indx,
                                            uint8_t dest_param_id,
                                            float scale,
                                            float offset,
                                            CTRL_NODE_TYPE type) {

  control_routing_stack[total_control_routes].src_id = src_id;
  control_routing_stack[total_control_routes].src_node_indx = src_node_indx;
  control_routing_stack[total_control_routes].src_param_id = src_param_id;
  control_routing_stack[total_control_routes].dest_id = dest_id;
  control_routing_stack[total_control_routes].dest_node_indx = dest_node_indx;
  control_routing_stack[total_control_routes].dest_param_id = dest_param_id;
  control_routing_stack[total_control_routes].scale = scale;
  control_routing_stack[total_control_routes].offset = offset;
  control_routing_stack[total_control_routes].type = type;

  total_control_routes++;

  return true;
}


bool fx_pedal::route_audio(fx_audio_node * src, fx_audio_node * dest) {

  bool local_debug = false;
  // Ensure inputs and outputs are valid
  if (src->node_direction != NODE_OUT || dest->node_direction != NODE_IN) {
    DEBUG_MSG("Source node is not an output, or destination node is not an input", MSG_ERROR);
    display_error_status(ERROR_CODE_ILLEGAL_ROUTING);
    return false;
  }

  uint8_t src_id, dest_id;

  // Set to false in case we bail mid-way through due to error
  valid_audio_routes = false;

  // Check to see if inputs and outputs are in our stack, and add if not
  if (src->parent_effect != NULL) {
    bool found = false;
    for (int i=0;i<total_instances;i++) {
      if ((void *) src->parent_effect == instance_stack[i].address) {
        found = true;
        src_id = i;
      }
    }
    if (!found) {
      instance_stack[total_instances].address = src->parent_effect;
      instance_stack[total_instances].type = src->parent_effect->get_type();
      instance_stack[total_instances].id = total_instances;
      src_id = total_instances;

      #if 0
        Serial.print("route_audio(): Adding new (source) instance type: ");
        Serial.print(instance_stack[total_instances].type);
        Serial.print(", address: ");
        Serial.println((uint32_t) instance_stack[total_instances].address, HEX);
      #endif


      // Set instance ID in this effect instance too
      src->parent_effect->instance_id = total_instances;

      total_instances++;

    }
  } else if (src->parent_canvas != NULL) {
    src_id = 0;
  }

  if (dest->parent_effect != NULL) {
    bool found = false;
    for (int i=0;i<total_instances;i++) {
      if ((void *) dest->parent_effect == instance_stack[i].address) {
        found = true;
        dest_id = i;
      }
    }
    if (!found) {
      instance_stack[total_instances].address = dest->parent_effect;
      instance_stack[total_instances].type = dest->parent_effect->get_type();
      instance_stack[total_instances].id = total_instances;
      dest_id = total_instances;

      #if 0
        Serial.print("route_audio(): Adding new (dest) instance type: ");
        Serial.print(instance_stack[total_instances].type);
        Serial.print(", address: ");
        Serial.println((uint32_t) instance_stack[total_instances].address, HEX);
      #endif

      // Set instance ID in this effect instance too
      dest->parent_effect->instance_id = total_instances;

      total_instances++;
    }
  } else if (dest->parent_canvas != NULL) {
    dest_id = 0;
  }

  // Add routes to our routing table
  uint8_t src_node_indx, dest_node_indx;
  bool res;

  // Look up source node
  if (src->parent_effect != NULL) {
    res = src->parent_effect->get_audio_node_index(src, &src_node_indx);
    if (!res) {
      DEBUG_MSG("Couldn't find this source node in the effect!", MSG_ERROR);
      display_error_status(ERROR_CODE_ILLEGAL_ROUTING);
      return false;
    }
  }
  else if (src->parent_canvas != NULL) {
    res = src->parent_canvas->get_audio_node_index(src, &src_node_indx);
    if (!res) {
      DEBUG_MSG("Couldn't find this source node in the canvas!", MSG_ERROR);
      display_error_status(ERROR_CODE_ILLEGAL_ROUTING);
      return false;
    }
  }

  // Look up destination node
  if (dest->parent_effect != NULL) {
    res = dest->parent_effect->get_audio_node_index(dest, &dest_node_indx);
    if (!res) {
      DEBUG_MSG("Couldn't find this destination node in the effect!", MSG_ERROR);
      display_error_status(ERROR_CODE_ILLEGAL_ROUTING);
      return false;
    }
  }
  else if (dest->parent_canvas != NULL) {
    res = dest->parent_canvas->get_audio_node_index(dest, &dest_node_indx);
    if (!res) {
      DEBUG_MSG("Couldn't find this destination node in the canvas!", MSG_ERROR);
      display_error_status(ERROR_CODE_ILLEGAL_ROUTING);
      return false;
    }
  }

  // Set both nodes to connected
  src->connected = true;
  dest->connected = true;

  // Add route to routing table
  add_audio_route_to_stack(src_id, src_node_indx, dest_id, dest_node_indx);

  // Check routing table for any outputs that are being written to twice
  for (int i=0;i<total_audio_routes;i++) {
    for (int j=0;j<total_audio_routes;j++) {
      if (i != j) {
        if ((audio_routing_stack[i].dest_id == audio_routing_stack[j].dest_id) &&
            (audio_routing_stack[i].dest_node_indx == audio_routing_stack[j].dest_node_indx)) {
          DEBUG_MSG("Two different effects writing to same audio node", MSG_ERROR);
          return false;
        }
      }
    }
  }

  // Happy days, we made it
  valid_audio_routes = true;
  return true;
}


bool fx_pedal::route_control(fx_control_node * src, fx_control_node * dest) {
  route_control(src, dest, 1.0, 0.0);
}

bool fx_pedal::route_control(fx_control_node * src, fx_control_node * dest, float scale, float offset) {

  // Set to false in case we bail mid-way through due to error
  valid_control_routes = false;

// Ensure inputs and outputs are valid
  if (src->node_direction != NODE_OUT || dest->node_direction != NODE_IN) {
    DEBUG_MSG("Source must be output, dest must be input", MSG_ERROR);
    display_error_status(ERROR_CODE_ILLEGAL_ROUTING);
    return false;
  }

  uint8_t src_id, dest_id;

  // Check to see if inputs and outputs are in our stack, and add if not
  if (src->parent_effect != NULL) {
    bool found = false;
    for (int i=0;i<total_instances;i++) {
      if ((void *) src->parent_effect == instance_stack[i].address) {
        found = true;
        src_id = i;
      }
    }
    if (!found) {
      instance_stack[total_instances].address = src->parent_effect;
      instance_stack[total_instances].type = src->parent_effect->get_type();
      instance_stack[total_instances].id = total_instances;
      src_id = total_instances;
      src->parent_effect->instance_id = total_instances;
      total_instances++;

      #if 0
        Serial.println(src->parent_effect->get_name());
      #endif
    }
  } else if (src->parent_canvas != NULL) {
    src_id = 0;
  }

  if (dest->parent_effect != NULL) {
    bool found = false;
    for (int i=0;i<total_instances;i++) {
      if ((void *) dest->parent_effect == instance_stack[i].address) {
        found = true;
        dest_id = i;
      }
    }
    if (!found) {
      instance_stack[total_instances].address = dest->parent_effect;
      instance_stack[total_instances].type = dest->parent_effect->get_type();
      instance_stack[total_instances].id = total_instances;
      dest_id = total_instances;
      dest->parent_effect->instance_id = total_instances;
      total_instances++;
    }
  } else if (src->parent_canvas != NULL) {
    dest_id = 0;
  }

  // Make sure controllers are compatible
  if (dest->node_type != src->node_type) {
    DEBUG_MSG("Trying to connect incompatible controls", MSG_ERROR);
    return false;
  }

  // Add routes to our routing table
  uint8_t src_node_indx, dest_node_indx;
  bool res;

  // Look up source node
  if (src->parent_effect != NULL) {
    res = src->parent_effect->get_control_node_index(src, &src_node_indx);
    if (!res) {
      DEBUG_MSG("Couldn't find the source node in an effect", MSG_ERROR);
      display_error_status(ERROR_CODE_ILLEGAL_ROUTING);
      return false;
    }
  }
  else if (src->parent_canvas != NULL) {
    if (src->parent_canvas)
    res = src->parent_canvas->get_control_node_index(src, &src_node_indx);
    if (!res) {
      DEBUG_MSG("Couldn't find the source node in the canvas", MSG_ERROR);
      display_error_status(ERROR_CODE_ILLEGAL_ROUTING);
      return false;
    }
  }

  // Look up destination node
  if (dest->parent_effect != NULL) {
    res = dest->parent_effect->get_control_node_index(dest, &dest_node_indx);
    if (!res) {
      DEBUG_MSG("Couldn't find the dest node in an effect", MSG_ERROR);
      display_error_status(ERROR_CODE_ILLEGAL_ROUTING);
      return false;
    }
  }
  else if (dest->parent_canvas != NULL) {
    if (dest->parent_canvas)
    res = dest->parent_canvas->get_control_node_index(dest, &dest_node_indx);
    if (!res) {
      DEBUG_MSG("Couldn't find the dest node in the canvas", MSG_ERROR);
      display_error_status(ERROR_CODE_ILLEGAL_ROUTING);
      return false;
    }
  }

  // Set both nodes to connected
  src->connected = true;
  dest->connected = true;

  // Add route to routing table
  add_control_route_to_stack(src_id, src_node_indx, src->param_id, dest_id, dest_node_indx, dest->param_id, scale, offset, dest->node_type);

  // Check routing table for any outputs that are being written to twice

  for (int i=0;i<total_control_routes;i++) {
    for (int j=0;j<total_control_routes;j++) {
      if (i != j) {
        if ((control_routing_stack[i].dest_id == control_routing_stack[j].dest_id) &&
            (control_routing_stack[i].dest_node_indx == control_routing_stack[j].dest_node_indx)) {
          DEBUG_MSG("Two different effects writing to same control node", MSG_ERROR);
          return false;
        }
      }
    }
  }

  // Happy days, we made it
  valid_control_routes = true;
  return true;

}


void fx_pedal::add_bypass_button(FOOTSWITCH footswitch) {
  bypass_control_enabled = true;
  pedal.bypassed = true;
  bypass_footswitch = footswitch;
}

void fx_pedal::add_tap_interval_button(FOOTSWITCH footswitch, bool enable_led_flash) {
  tap_control_enabled = true;
  if (bypass_footswitch == footswitch) {
    DEBUG_MSG("Attempting to add tap interval to footswitch already used for bypass", MSG_ERROR);
    return;
  }

  tap_led_flash = enable_led_flash;
  tap_footswitch = footswitch;
  tap_indx = 0;
  tap_last_tap = millis();

}


#ifndef DOXYGEN_SHOULD_SKIP_THIS

bool  fx_pedal::get_audio_node_index(fx_audio_node * node, uint8_t * node_index) {

  for (int i=0;i<MAX_NODES_PER_FX;i++) {

    if (node == audio_node_stack[i]) {
      *node_index = i;
      return true;
    }
  }
  return false;
}


bool  fx_pedal::get_control_node_index(fx_control_node * node, uint8_t * node_index) {

  for (int i=0;i<MAX_NODES_PER_FX;i++) {

    if (node == control_node_stack[i]) {
      *node_index = i;
      return true;
    }
  }
  return false;
}

#endif  // DOXYGEN_SHOULD_SKIP_THIS







/******************************************************************************
 *  Canvas control functions
 *****************************************************************************/


void fx_pedal::service(void) {

  if (tap_control_enabled) {
    if (tap_footswitch == FOOTSWITCH_LEFT) {
      if (millis() < tap_led_flash_cntr + 50) {
        turn_on_left_footsw_led();
      } else {
        turn_off_left_footsw_led();
      }
    }
    else if (tap_footswitch == FOOTSWITCH_RIGHT) {
      if (millis() < tap_led_flash_cntr + 50) {
        turn_on_right_footsw_led();
      } else {
        turn_off_right_footsw_led();
      }
    }
  }

  if ((tap_blink_only_enabled || tap_control_enabled) && tap_locked) {
    if (millis() > tap_led_flash_cntr + tap_interval_ms) {
      tap_led_flash_cntr = millis();
      if (tap_footswitch == FOOTSWITCH_LEFT) {
        turn_on_left_footsw_led();
      } else if (tap_footswitch == FOOTSWITCH_RIGHT) {
        turn_on_right_footsw_led();
      }
    } else if (millis() > tap_led_flash_cntr + 200) {
      if (tap_footswitch == FOOTSWITCH_LEFT) {
        turn_off_left_footsw_led();
      } else if (tap_footswitch == FOOTSWITCH_RIGHT) {
        turn_off_right_footsw_led();
      }
    }
  }

  if (dsp_status.state_err_other) {
    DEBUG_MSG("Internal error detected on DSP - try resetting", MSG_WARN);
    dsp_status.state_err_other = false;
    delay(1000);
  }

  // Service LEDs
  led_left.service();
  #if defined (DM_FX_TWO)
    led_center.service();
  #endif
  led_right.service();

  button_press_check();
  service_button_events();

  // Read in telemetry data from the DSP
  display_data_from_sharc();

  // Run the remainder of service loop ~30 times / second
  if (millis() < last_service_ts + 33) return;
  last_service_ts = millis();

  // Read the pots and switches
  #if defined (DM_FX)
    pot_right.read_pot();
    pot_center.read_pot();
    pot_left.read_pot();

  #elif defined (DM_FX_TWO)
    pot_top_left.read_pot();
    pot_top_right.read_pot();
    pot_bot_left.read_pot();
    pot_bot_center.read_pot();
    pot_bot_right.read_pot();
    exp_pedal.read_pot();
    toggle_left.read_switch();
    toggle_right.read_switch();

  #endif

  // Get status data from the DSP
  spi_get_status();

  // Service any parameter updates
  spi_service();

}

void    fx_pedal::bypass_fx(void) {
  DEBUG_MSG("Bypass", MSG_DEBUG);
  spi_transmit_bypass((uint16_t) 1);
}


void    fx_pedal::enable_fx(void) {
  DEBUG_MSG("Enable", MSG_DEBUG);
  spi_transmit_bypass((uint16_t) 0);
}


bool fx_pedal::run(void) {


  bool ready = true;

  // Check to see if our routing is valid
  if (total_audio_routes == 0) {
    DEBUG_MSG("No routes defined", MSG_ERROR);
    display_error_status(ERROR_CODE_ILLEGAL_ROUTING);
  } else if (!valid_audio_routes) {
    DEBUG_MSG("Errors in the audio routing.  Fix errors in your route_audio() calls.", MSG_ERROR);
    display_error_status(ERROR_CODE_ILLEGAL_ROUTING);
  } else if (total_control_routes > 0 && !valid_control_routes) {
    DEBUG_MSG("Errors in the control routing.  Fix errors in your route_control() calls.", MSG_ERROR);
    display_error_status(ERROR_CODE_ILLEGAL_ROUTING);
  }

  if (ready) {
    display_data_from_sharc();

    // Send routing stack to DSP
    spi_transmit_audio_routing_stack();
    display_data_from_sharc();

    spi_transmit_control_routing_stack();
    display_data_from_sharc();

    // Send instance stack to DSP
    spi_transmit_instance_stack();
    display_data_from_sharc();

    // Send parameters to DSP
    spi_transmit_all_params();
    display_data_from_sharc();

    // If we've added bypass controls, start effect bypassed
    if (bypass_control_enabled) {
      pedal.bypassed = true;
      bypass_fx();
    } else {
      pedal.bypassed = false;
      enable_fx();
    }

    // Wait for DSP to send message that canvas is running
    wait_for_canvas_to_start();
    int now = millis();
    while (millis() < now + 50) {
      display_data_from_sharc();
    }



    if (dsp_status.state_canvas_running) {
      DEBUG_MSG("Canvas is running", MSG_INFO);
    } else {
      report_canvas_errors();
      ready = false;
    }
  }

  // Display error code on LEDs
  if (!ready) {
    display_error_status(ERROR_CODE_ILLEGAL_ROUTING);
  }

  return ready;

}




/******************************************************************************
 *  Parameter control functions
 *****************************************************************************/




#ifndef DOXYGEN_SHOULD_SKIP_THIS

void fx_pedal::register_tap(void) {

  uint32_t interval = millis() - tap_last_tap;
  tap_last_tap = millis();
  tap_led_flash_cntr = millis();

  #if 0
    Serial.print("Debug: interval:");
    Serial.println(interval);
  #endif

  if (tap_indx == 0 || interval > 2000) {

    tap_indx = 1;
    tap_locked = false;
    tap_new_val = false;

    for (int i=0;i<15;i++) {
      tap_history[i] = 0;
    }

    return;
  } else if (tap_indx < 2) {
    tap_history[tap_indx++] = interval;
    tap_locked = false;
  } else {
    tap_history[tap_indx++] = interval;
    if (tap_indx >= 16) {
      tap_indx = 15;
    }
    float sum = 0.0;
    for (int i=1; i<tap_indx; i++) {
      sum += (float) tap_history[i];
    }
    sum *= 1.0/((float) (tap_indx-1));
    tap_locked = true;
    tap_interval_ms = sum;
    tap_new_val = true;
    #if 0
      Serial.print("Debug: tap ms:");
      Serial.println(tap_interval_ms);
    #endif
  }
}

void fx_pedal::button_press_check(void) {
  static int time_since_last_press = 0;
  if (millis() < time_since_last_press + 75) {
    return;
  }
  time_since_last_press = millis();

  bool left = !digitalRead(PIN_FOOTSW_LEFT);
  bool right = !digitalRead(PIN_FOOTSW_RIGHT);

  if (left && left != footswitch_left_last_state) {
    footswitch_left_pressed = true;
  } else if (!left && left != footswitch_left_last_state) {
    footswitch_left_released = true;
  }

  if (right && right != footswitch_right_last_state) {
    footswitch_right_pressed = true;
  } else if (!right && right != footswitch_right_last_state) {
    footswitch_right_released = true;
  }

  footswitch_left_last_state = left;
  footswitch_right_last_state = right;
}

void  fx_pedal::service_button_events(void) {

  if (footswitch_right_pressed_event) {
    footswitch_right_pressed_event = false;

    if (bypass_control_enabled && bypass_footswitch == FOOTSWITCH_RIGHT) {

        DEBUG_MSG("Toggle bypass", MSG_DEBUG);

        if (bypassed) {
          led_right.turn_on();
          enable_fx();
        } else {
          led_right.turn_off();
          bypass_fx();
        }
        bypassed = !bypassed;
      }
  }

  if (footswitch_left_pressed_event) {
    footswitch_left_pressed_event = false;
    if (bypass_control_enabled && bypass_footswitch == FOOTSWITCH_LEFT) {

      DEBUG_MSG("Toggle bypass", MSG_DEBUG);

      if (bypassed) {
        //Serial.println("Turning on left LED");
        led_left.turn_on();
        //turn_on_left_footsw_led();
        enable_fx();
      } else {
        //Serial.println("Turning off left LED");
        led_left.turn_off();
        //turn_off_left_footsw_led();
        bypass_fx();
      }
      bypassed = !bypassed;
    }
  }
}

#endif // DOXYGEN_SHOULD_SKIP_THIS



bool fx_pedal::new_tap_interval(void) {
  if (tap_new_val) {
    tap_new_val = false;
    return true;
  } else {
    return false;
  }
}


float fx_pedal::get_tap_interval_ms(void) {
  if (tap_locked) {
    return tap_interval_ms;
  } else {
    return 1000.0;
  }
}

float fx_pedal::get_tap_freq_hz(void) {
  if (tap_locked) {
    return 1.0/(0.001 * tap_interval_ms);
  } else {
    return 1.0;
  }
}


void fx_pedal::set_tap_blink_rate_hz(float rate_hz) {
  if (rate_hz < 0) {
    rate_hz = 0;
  } else if (rate_hz > 100) {
    rate_hz = 100.0;
  }
  tap_interval_ms = (1000.0/rate_hz);
  tap_locked = true;
  tap_control_enabled = true;
}

void fx_pedal::set_tap_blink_rate_hz(float rate_hz, FOOTSWITCH led) {
  if (rate_hz < 0) {
    rate_hz = 0;
  } else if (rate_hz > 100) {
    rate_hz = 100.0;
  }
  tap_interval_ms = (1000.0/rate_hz);
  tap_locked = true;
  tap_blink_only_enabled = true;
  tap_footswitch = led;

}


void fx_pedal::set_tap_blink_rate_ms(float ms) {
  if (ms < 0) {
    ms = 10;
  } else if (ms > 10000) {
    ms = 10000.0;
  }
  tap_interval_ms = ms;
  tap_locked = true;
  tap_control_enabled = true;
}

void fx_pedal::set_tap_blink_rate_ms(float ms, FOOTSWITCH led) {
  if (ms < 0) {
    ms = 10;
  } else if (ms > 10000) {
    ms = 10000.0;
  }
  tap_interval_ms = ms;
  tap_locked = true;
  tap_blink_only_enabled = true;
  tap_footswitch = led;
}



bool fx_pedal::button_pressed(FOOTSWITCH footswitch, bool enable_led) {
  bool result = false;

  if (footswitch == FOOTSWITCH_BOTH && footswitch_left_pressed && footswitch_right_pressed) {
    footswitch_left_pressed = false;
    footswitch_left_released = false;
    footswitch_right_pressed = false;
    footswitch_right_released = false;
    result = true;
  } else if (footswitch == FOOTSWITCH_LEFT && footswitch_left_pressed) {
    footswitch_left_pressed = false;
    footswitch_left_released = false;
    if (enable_led) {
      led_left.turn_on();
    }
    result = true;
  } else if (footswitch == FOOTSWITCH_RIGHT && footswitch_right_pressed) {
    footswitch_right_pressed = false;
    footswitch_right_released = false;
    if (enable_led) {
      led_right.turn_on();
    }
    result = true;
  }
  return result;
}

bool fx_pedal::button_released(FOOTSWITCH footswitch, bool enable_led) {
  bool result = false;
  if (footswitch == FOOTSWITCH_LEFT && footswitch_left_released) {
    footswitch_left_pressed = false;
    footswitch_left_released = false;
    if (enable_led) {
      led_left.turn_off();
      //turn_off_left_footsw_led();
    }
    result = true;
  } else if (footswitch == FOOTSWITCH_RIGHT && footswitch_right_released) {
    footswitch_right_pressed = false;
    footswitch_right_released = false;
    if (enable_led) {
      led_right.turn_off();
      //turn_off_right_footsw_led();
    }
    result = true;
  }
  return result;
}










void fx_pedal::print_processor_load(int seconds) {
  if (seconds < 1) {
    seconds = 1;
  }
  static int now = 0;
  if (millis() > now + seconds*1000) {
    Serial.print("Processor load: ");
    Serial.print(dsp_status.loading_percentage);
    Serial.println("%");
    now = millis();
  }
}





void fx_pedal::print_instance_stack() {

  char buf[64];

  Serial.println();
  Serial.println("Instance stack:");

  sprintf(buf," Total instances: %d", (int) total_instances); Serial.println(buf);
  for (int i=0;i<total_instances;i++) {
    if (instance_stack[i].type != FX_UNDEFINED) {

      sprintf(buf," ID: %#04x", (int) instance_stack[i].id); Serial.println(buf);

      Serial.print("  Type: ");
      Serial.print(get_effect_type(instance_stack[i].type));
      Serial.print(" (");
      Serial.print(instance_stack[i].type);
      Serial.println(")");

      sprintf(buf,"  Address: %#04x", (int) instance_stack[i].address); Serial.println(buf);

    } else {
      Serial.println("Undefined instance found");
    }
  }
  Serial.println();
}

void fx_pedal::print_routing_table() {

  char buf[64];

  Serial.println();
  Serial.println("Audio routing table:");

  if (total_audio_routes > 0) {
    for (int i=0;i<total_audio_routes;i++) {
      if (audio_routing_stack[i].src_id != UNDEFINED) {
        Serial.print(" Src ID: ");
        Serial.println((int) audio_routing_stack[i].src_id, HEX);
        Serial.print("  Src Node Indx: ");
        Serial.println((int) audio_routing_stack[i].src_node_indx, HEX);
        Serial.print(" Dest ID: ");
        Serial.println((int) audio_routing_stack[i].dest_id, HEX);
        Serial.print("  Dest Node Indx: ");
        Serial.println((int) audio_routing_stack[i].dest_node_indx, HEX);
        Serial.println();
      }
    }
  } else {
    Serial.println(" No audio routes in canvas");
  }

  Serial.println("Control routing table:");

  if (total_control_routes > 0) {
    for (int i=0;i<total_control_routes;i++) {
      if (audio_routing_stack[i].src_id != UNDEFINED) {
        Serial.print(" Src ID: ");
        Serial.println((int) control_routing_stack[i].src_id, HEX);
        Serial.print("  Src Node Indx: ");
        Serial.println((int) control_routing_stack[i].src_node_indx, HEX);
        Serial.print(" Dest ID: ");
        Serial.println((int) control_routing_stack[i].dest_id, HEX);
        Serial.print("  Dest Node Indx: ");
        Serial.println((int) control_routing_stack[i].dest_node_indx, HEX);
        Serial.println();
      }
    }
  } else {
    Serial.println(" No control routes in canvas");
  }


}

void fx_pedal::print_param_tables() {
  char buf[64];

  Serial.println();
  Serial.println("Parameter tables:");

  if (total_instances <= 1) {
    Serial.println(" There are no effect instances in this canvas so there are no parameters");
    return;
  }

  for (int i=1;i<total_instances;i++) {
    if (instance_stack[i].address == NULL) {
      sprintf(buf, "Null pointer encoundered for instance %d", i); Serial.println(buf);
    } else {

      if (instance_stack[i].type == FX_ALLPASS_FILTER) {
        sprintf(buf, "Allpass filter (instance %d)", i); Serial.println(buf);
        fx_allpass_filter * effect = (fx_allpass_filter *) instance_stack[i].address;
        effect->print_params();
      }

      else if (instance_stack[i].type == FX_ADSR_ENVELOPE) {
        sprintf(buf, "ADSR Envelope (instance %d)", i); Serial.println(buf);
        fx_adsr_envelope * effect = (fx_adsr_envelope *) instance_stack[i].address;
        effect->print_params();
      }

      else if (instance_stack[i].type == FX_AMPLITUDE_MODULATOR) {
        sprintf(buf, "AMP MODULATOR (instance %d)", i); Serial.println(buf);
        fx_amplitude_mod * effect = (fx_amplitude_mod *) instance_stack[i].address;
        effect->print_params();
      }

      else if (instance_stack[i].type == FX_ARPEGGIATOR) {
        sprintf(buf, "Arpeggiator (instance %d)", i); Serial.println(buf);
        fx_arpeggiator * effect = (fx_arpeggiator *) instance_stack[i].address;
        effect->print_params();
      }

      else if (instance_stack[i].type == FX_BIQUAD_FILTER) {
        sprintf(buf, "BIQUAD (instance %d)", i); Serial.println(buf);
        fx_biquad_filter * effect = (fx_biquad_filter *) instance_stack[i].address;
        effect->print_params();
      }

      else if (instance_stack[i].type == FX_DESTRUCTOR) {
        sprintf(buf, "Destructor (instance %d)", i); Serial.println(buf);
        fx_destructor * effect = (fx_destructor *) instance_stack[i].address;
        effect->print_params();
      }

      else if (instance_stack[i].type == FX_COMPRESSOR) {
        sprintf(buf, "Compressor (instance %d)", i); Serial.println(buf);
        fx_compressor * effect = (fx_compressor *) instance_stack[i].address;
        effect->print_params();
      }

      else if (instance_stack[i].type == FX_DELAY) {
        sprintf(buf, "DELAY (instance %d)", i); Serial.println(buf);
        fx_delay * effect = (fx_delay *) instance_stack[i].address;
        effect->print_params();
      }

      else if (instance_stack[i].type == FX_DELAY_MULTITAP) {
        sprintf(buf, "MULTITAP DELAY (instance %d)", i); Serial.println(buf);
        fx_multitap_delay * effect = (fx_multitap_delay *) instance_stack[i].address;
        effect->print_params();
      }

      else if (instance_stack[i].type == FX_ENVELOPE_TRACKER) {
        sprintf(buf, "ENVELOPE TRACKER (instance %d)", i); Serial.println(buf);
        fx_envelope_tracker * effect = (fx_envelope_tracker *) instance_stack[i].address;
        effect->print_params();
      }


      else if (instance_stack[i].type == FX_GAIN) {
        sprintf(buf, "GAIN (instance %d)", i); Serial.println(buf);
        fx_gain * effect = (fx_gain *) instance_stack[i].address;
        effect->print_params();
      }

      else if (instance_stack[i].type == FX_HARMONIZER) {
        sprintf(buf, "HARMONIZER (instance %d)", i); Serial.println(buf);
        fx_harmonizer * effect = (fx_harmonizer *) instance_stack[i].address;
        effect->print_params();
      }

      else if (instance_stack[i].type == FX_IMPULSE_RESPONSE) {
        sprintf(buf, "IMPULSE RESPONSE (instance %d)", i); Serial.println(buf);
        fx_impulse_response * effect = (fx_impulse_response *) instance_stack[i].address;
        effect->print_params();
      }


      else if (instance_stack[i].type == FX_MIXER_2) {
        sprintf(buf, "MIXER x 2 (instance %d)", i); Serial.println(buf);
        fx_mixer_2 * effect = (fx_mixer_2 *) instance_stack[i].address;
        effect->print_params();
      }

      else if (instance_stack[i].type == FX_MIXER_3) {
        sprintf(buf, "MIXER x 3 (instance %d)", i); Serial.println(buf);
        fx_mixer_3 * effect = (fx_mixer_3 *) instance_stack[i].address;
        effect->print_params();
      }

     else  if (instance_stack[i].type == FX_MIXER_4) {
        sprintf(buf, "MIXER x 4 (instance %d)", i); Serial.println(buf);
        fx_mixer_4 * effect = (fx_mixer_4 *) instance_stack[i].address;
        effect->print_params();
      }

      else if (instance_stack[i].type == FX_PHASE_SHIFTER) {
        sprintf(buf, "PHASE SHIFTER (instance %d)", i); Serial.println(buf);
        fx_phase_shifter * effect = (fx_phase_shifter *) instance_stack[i].address;
        effect->print_params();
      }

      else if (instance_stack[i].type == FX_PITCH_SHIFT) {
        sprintf(buf, "PITCH SHIFT (instance %d)", i); Serial.println(buf);
        fx_pitch_shift * effect = (fx_pitch_shift *) instance_stack[i].address;
        effect->print_params();
      }

      else if (instance_stack[i].type == FX_RING_MOD) {
        sprintf(buf, "RING MODULATOR (instance %d)", i); Serial.println(buf);
        fx_ring_mod * effect = (fx_ring_mod *) instance_stack[i].address;
        effect->print_params();
      }

      else if (instance_stack[i].type == FX_SLICER) {
        sprintf(buf, "SLICER (instance %d)", i); Serial.println(buf);
        fx_slicer * effect = (fx_slicer *) instance_stack[i].address;
        effect->print_params();
      }

      else if (instance_stack[i].type == FX_LOOPER) {
        sprintf(buf, "LOOPER (instance %d)", i); Serial.println(buf);
        fx_looper * effect = (fx_looper *) instance_stack[i].address;
        effect->print_params();
      }

      else if (instance_stack[i].type == FX_INSTRUMENT_SYNTH) {
        sprintf(buf, "INSTRUMENT SYNTH (instance %d)", i); Serial.println(buf);
        fx_instrument_synth * effect = (fx_instrument_synth *) instance_stack[i].address;
        effect->print_params();
      }

      else if (instance_stack[i].type == FX_OSCILLATOR) {
        sprintf(buf, "OSCILLATOR (instance %d)", i); Serial.println(buf);
        fx_oscillator * effect = (fx_oscillator *) instance_stack[i].address;
        effect->print_params();
      }


      else if (instance_stack[i].type == FX_VARIABLE_DELAY) {
        sprintf(buf, "VARIABLE DELAY (instance %d)", i); Serial.println(buf);
        fx_variable_delay * effect = (fx_variable_delay *) instance_stack[i].address;
        effect->print_params();
      }

      else {
        sprintf(buf, "UNKNOWN (instance %d)", i); Serial.println(buf);
        fx_effect * effect = (fx_effect *) instance_stack[i].address;
        effect->print_params();
      }
    }
  }
}

#ifndef DOXYGEN_SHOULD_SKIP_THIS

char * fx_pedal::get_effect_type(EFFECT_TYPE t) {
  if (t == FX_NONE) return "none";
  else if (t == FX_ADSR_ENVELOPE) return "adsr envelope";
  else if (t == FX_ALLPASS_FILTER) return "allpass filter";
  else if (t == FX_AMPLITUDE_MODULATOR) return "amplitude modulator";
  else if (t == FX_ARPEGGIATOR) return "arpeggiator";
  else if (t == FX_BIQUAD_FILTER) return "biquad filter";
  else if (t == FX_DESTRUCTOR) return "destructor";
  else if (t == FX_COMPRESSOR) return "compressor";
  else if (t == FX_DELAY) return "delay";
  else if (t == FX_DELAY_MULTITAP) return "multitap delay";
  else if (t == FX_ENVELOPE_TRACKER) return "envelope tracker";
  else if (t == FX_GAIN) return "gain";
  else if (t == FX_IMPULSE_RESPONSE) return "impulse response";
  else if (t == FX_INSTRUMENT_SYNTH) return "instrument synth";
  else if (t == FX_LOOPER) return "looper";
  else if (t == FX_MIXER_2) return "mixer x 2";
  else if (t == FX_MIXER_3) return "mixer x 3";
  else if (t == FX_MIXER_4) return "mixer x 4";
  else if (t == FX_OSCILLATOR) return "oscillator";
  else if (t == FX_PHASE_SHIFTER) return "phase shifter";
  else if (t == FX_PITCH_SHIFT) return "pitch shift";
  else if (t == FX_RING_MOD) return "ring modulator";
  else if (t == FX_SLICER) return "slicer";
  else if (t == FX_SPECTRALIZER) return "spectralizer";
  else if (t == FX_VARIABLE_DELAY) return "variable delay";
  else if (t == FX_UNDEFINED) return "undefined";
  else if (t == FX_CANVAS) return "canvas";
  else return "unknown";
}

#endif




















/************************************************************************
 *
 *                        EFFECTS FUNCTIONS
 *
 ***********************************************************************/


bool fx_effect::get_audio_node_index(fx_audio_node * node, uint8_t * local_node_index) {

  for (int i=0;i<MAX_NODES_PER_FX;i++) {

    if (node == audio_node_stack[i]) {
      *local_node_index = i;

      // save in instance
      node_index = i;
      return true;
    }
  }
  return false;
}

bool fx_effect::get_control_node_index(fx_control_node * node, uint8_t * local_node_index) {


  for (int i=0;i<MAX_NODES_PER_FX;i++) {

    if (node == control_node_stack[i]) {
      *local_node_index = i;

      // save in instance
      node_index = i;
      return true;
    }
  }
  return false;
}


uint16_t * fx_effect::serialize_params(uint16_t * serialized_params, uint16_t * size) {

  // serialize instance data
  int indx = 0;

  uint32_t part_32;
  uint16_t part_16;
  uint8_t part_8;

  for (int i=0;i<total_params;i++) {

    if (param_stack_types[i] == T_BOOL) {
     // Serial.println(" : bool");
      part_16 = * (uint8_t *) param_stack[i];
      serialized_params[indx++] = part_16;
    }
    else if (param_stack_types[i] == T_INT16) {
      //Serial.println(" : int16");
      //Serial.println((int)param_stack[i],HEX);
      serialized_params[indx++] = (uint16_t) (* (uint16_t *) param_stack[i]);
    }
    else if (param_stack_types[i] == T_INT32) {
      //Serial.println(" : int32");
      part_32 = * (uint32_t *) param_stack[i];
      serialized_params[indx++] = (uint16_t)((* (uint32_t *) param_stack[i]) >> 16);
      serialized_params[indx++] = (uint16_t)((* (uint32_t *) param_stack[i]) & 0xFFFF);
    }
    else if (param_stack_types[i] == T_FLOAT) {
      //Serial.println(" : float");
      part_32 = * (uint32_t *) param_stack[i];
      serialized_params[indx++] = (uint16_t) (part_32 >> 16);
      serialized_params[indx++] = (uint16_t) (part_32 & 0xFFFF);
    }

    if (indx > MAX_PARMS_PER_FX-5) {
      Serial.print("Error with ");
      Serial.println(effect_name);
      DEBUG_MSG("Maximum parameter limit (MAX_PARMS_PER_FX) exceeded", MSG_ERROR);
      display_error_status(ERROR_INTERNAL);
    }

    //sprintf(buf,"  %#08x - %d", param_stack[i], (int) param_stack_types[i]); Serial.println(buf);

  }
  *size = indx;
  #if 0
    Serial.println("  ------");

    for (int i=0;i<indx;i++) {
      //sprintf(buf,"  %#04x, [%#08x] -> %#04x - %d", serialized_params[i], param_stack[i], * (uint16_t *) param_stack[i], (int) param_stack_types[i]); Serial.println(buf);
      sprintf(buf,"  %#04x", serialized_params[i]); Serial.println(buf);
    }
    Serial.println("Complete");
    //sprintf(buf, "Bool: %d" , (int) sizeof(bool)); Serial.println(buf);
  #endif
}

bool  fx_effect::float_param_updated( float * param, float * param_last, float threshold ) {

  bool different;
  if (abs(*param - *param_last) > threshold) {
    different = true;
  } else {
    different = false;
  }

  *param_last = *param;

  return different;

}

bool  fx_effect::bool_param_updated( bool * param, bool * param_last ) {
  bool different = false;
  if (*param != *param_last) {
    different = true;
  }
  *param_last = *param;
  return different;
}


void  fx_effect::print_params(void) {
  Serial.println(" No print function declared for this effect");
}

bool  fx_effect::service(void) {
  Serial.println(" No service function declared for this effect");
  return false;
}






/************************************************************************
 *
 *                        LED FUNCTIONS
 *
 ***********************************************************************/


#ifndef DOXYGEN_SHOULD_SKIP_THIS

fx_led::fx_led(LED_POS pos) {
  led_pos = pos;
  last_scan = millis();
}


void  fx_led::update_rgb_led(void) {
  rgb_write((int) led_pos, (uint8_t) cur_r, (uint8_t) cur_g, (uint8_t) cur_b);
}

void  fx_led::service() {
  if (millis() >= last_scan + LED_UPDATE_RATE_MS) {
    last_scan = millis();

    if (steps) {

      cur_r += inc_r;
      cur_g += inc_g;
      cur_b += inc_b;

      update_rgb_led();

      steps--;
    }
  }
}
#endif // DOXYGEN_SHOULD_SKIP_THIS



void  fx_led::turn_on() {

  switch(led_pos) {
    case LED_RIGHT:
      turn_on_right_footsw_led(); break;
    case LED_CENTER:
      turn_on_center_footsw_led(); break;
    case LED_LEFT:
      turn_on_left_footsw_led(); break;
    default: break;
  }
}

void  fx_led::turn_on(uint8_t red, uint8_t green, uint8_t blue) {
  set_rgb(red, green, blue);
}

void  fx_led::turn_on(LED_COLOR rgb) {
  set_rgb(rgb);
}


void  fx_led::turn_off() {
  switch(led_pos) {
    case LED_RIGHT: turn_off_right_footsw_led(); break;
    case LED_CENTER: turn_off_center_footsw_led(); break;
    case LED_LEFT: turn_off_left_footsw_led(); break;
    default: break;
  }
}

void  fx_led::set_rgb(uint8_t red, uint8_t green, uint8_t blue) {
  #if defined (DM_FX)
    turn_on();
  #elif defined (DM_FX_TWO)
    cur_r = red;
    cur_g = green;
    cur_b = blue;
    steps = 0;
    update_rgb_led();
  #endif
}

void  fx_led::set_rgb(LED_COLOR rgb) {

  #if defined (DM_FX_TWO)
    float red = ((uint32_t) rgb >> 16) & 0xFF;
    float green = ((uint32_t) rgb >> 8) & 0xFF;
    float blue = (uint32_t) rgb & 0xFF;
    set_rgb(red, green, blue);
  #endif
}

void  fx_led::fade_to_rgb(uint8_t red, uint8_t green, uint8_t blue, uint32_t milliseconds) {
  #if defined (DM_FX_TWO)
    target_r = red;
    target_g = green;
    target_b = blue;

    steps = (uint32_t) ((float) milliseconds * (1.0/LED_UPDATE_RATE_MS));
    float inc = 1.0/(float) steps;

    inc_r = (target_r - cur_r) * inc;
    inc_g = (target_g - cur_g) * inc;
    inc_b = (target_b - cur_b) * inc;

    update_rgb_led();
  #endif

}

void  fx_led::fade_to_rgb(LED_COLOR rgb, uint32_t milliseconds) {
  float red = (rgb >> 16) & 0xFF;
  float green = (rgb >> 8) & 0xFF;
  float blue = rgb & 0xFF;
  fade_to_rgb(red, green, blue, milliseconds);

}