korginc / logue-sdk

This repository contains all the files and tools needed to build custom oscillators and effects for the prologue synthesizer.
BSD 3-Clause "New" or "Revised" License
835 stars 306 forks source link

OSC_PARAM changes all parameters to zero before first OSC_CYCLE call #35

Open boochow opened 4 years ago

boochow commented 4 years ago

Describe the bug (It may not be a bug but an issue of API specification. Anyway, I report this because someone might come into the same issue or have some solution for this issue.

I set a global variable to a non-zero value in OSC_INIT(). The variable also can be modified from OSC_PARAM(). At the first OSC_CYCLE() call, the variable is always zero. If I remove the lines modifying the variable from OSC_PARAM(), the issue would disappear. I think this is because OSC_PARAM() is invoked with all parameters are zero' ed, after OSC_INIT() and before the first OSC_CYCLE() call,
So, how can I initialize parameters in OSC_INIT() with keeping it modifiable from OSC_PARAM()?

To Reproduce

#include "userosc.h"

#define SINE   0
#define SQUARE 1

typedef struct State {
  float phase;
  uint8_t wave;
} State;

static State s_state;

void OSC_INIT(uint32_t platform, uint32_t api)
{
  s_state.phase = 0.f;
  s_state.wave = SQUARE;          /* wave is set to SQUARE, but... */
}

void OSC_CYCLE(const user_osc_param_t * const params,
               int32_t *yn,
               const uint32_t frames)
{
  const float w0 = osc_w0f_for_note((params->pitch)>>8, params->pitch & 0xFF);
  float phase = s_state.phase;

  q31_t * __restrict y = (q31_t *)yn;
  const q31_t * y_e = y + frames;

  for (; y != y_e; ) {
    float sig;
    switch (s_state.wave) {        /* wave is ALWAYS SINE ! */
    case SINE:
      sig = osc_sinf(phase);
      break;
    case SQUARE:
      sig = osc_sqrf(phase);
      break;
    }

    *(y++) = f32_to_q31(sig);

    phase += w0; phase -= (uint32_t)phase;
  }
  s_state.phase = phase;
}

void OSC_NOTEON(const user_osc_param_t * const params){}
void OSC_NOTEOFF(const user_osc_param_t * const params){}

void OSC_PARAM(uint16_t index, uint16_t value) {
  switch (index) {
  case k_user_osc_param_id1:
    s_state.wave = value;          /* removing this line solves the problem */
    break;
  default:
    break;
  }
}

Expected behavior OSC_PARAM() initializes all parameters to "initial values," which should be specified in the manifest file.

Desktop (please complete the following information):

h2g2guy commented 4 years ago

@boochow -- I think it's reasonable that OSC_PARAM is being called upon initialization of the oscillator program. If your oscillator program is used as part of a preset on the prologue or minilogue xd, OSC_PARAM is likely the way that the correct parameter values get set when that preset is recalled.

There are bugs related to this behavior though, particularly with bipolar parameters. Please see my bug #36 .

boochow commented 4 years ago

@h2g2guy thank you for your comment! I agree with you that OSC_PARAM should set the parameters to the appropriate value when a preset program is loaded. It is a completely reasonable behavior.

My point is that I want to define initial values of parameters for NTS1, which is not able to have any preset programs. Currently, the SDK always sets all parameters for their minimum values. I also think that maybe the initial-value feature would be useful for Prologue/Minilouge xd because it could give default parameter values for a new patch.