const NUMBER_OF_VOICES = 8;

const keys = [1, 2, 3, 4];
const vels = ['Soft', 'MedSoft', 'MedLoud', 'Loud'];
const velsReversed = [...vels].reverse();

const getBestSample = (sampleSpecs, pitch, velocity) => {
  let bestSample;
  for (let i = 0; i < sampleSpecs.length; i++) {
    const sample = sampleSpecs[i];
    if (!bestSample) {
      bestSample = sample;
    } else if (Math.abs(sample.pitch - (pitch + 3)) < Math.abs(bestSample.pitch - (pitch + 3))) {
      bestSample = sample;
    } else if (Math.abs(sample.velocity - velocity) < Math.abs(bestSample.velocity - velocity)) {
      bestSample = sample;
    }
  }
  return bestSample;
};

const interpolatedRead = (buffer, index) => {
  if (buffer.length <= 1) {
    return 0;
  }
  const prev = Math.max(0, Math.floor(index));
  const next = Math.min(buffer.length - 1, prev + 1);
  const amountOfNextSample = index - prev;
  const amountOfPrevSample = 1 - amountOfNextSample;
  return buffer[prev] * amountOfPrevSample + buffer[next] * amountOfNextSample;
};

const useMultiSampleVoice = (sampleSpecs, voice, output) => {
  const sampleProgress = useVal(0);
  const sampleSpec = useVal(null);
  const playbackRate = useVal(0);
  for (let i = 0; i < output[0].length; i++) {
    if (voice.trigger[i] > 0.5) {
      sampleSpec.current = getBestSample(sampleSpecs, voice.pitch[i], voice.velocity[i] * 127);
      sampleProgress.current = 0;
      playbackRate.current = Math.pow(2, ((voice.pitch[i] + sampleSpec.current.tune[i]) - sampleSpec.current.pitch) / 12);
    } else if (sampleSpec.current) {
      sampleProgress.current += playbackRate.current;
    }

    if (sampleSpec.current && sampleProgress.current < sampleSpec.current.sample.channelDataList[0].length) {
      const cdl = sampleSpec.current.sample.channelDataList;
      const leftChannel = cdl[0];
      const rightChannel = cdl.length > 1 ? cdl[1] : cdl[0];
      output[0][i] = interpolatedRead(leftChannel, sampleProgress.current);
      output[1][i] = interpolatedRead(rightChannel, sampleProgress.current);
    }
  }
};

const getSampleSpecs = (settings) => {
  const result = [];
  keys.forEach((k) => {
    vels.forEach((v) => {
      result.push({
        pitch: settings[`sample${k}Pitch`],
        velocity: settings[`${v}Velocity`],
        sample: settings[`sample${k}${v}`],
        tune: settings[`sample${k}${v}Tune`]
      });
    });
  });
  return result;
};

const SimpleSynth = (settings) => {
  const { input, attack, decay, sustain, release, output, gain } = settings;
  output.forEach((c) => c.fill(0));
  const voices = useRoundRobinVoicing(input, NUMBER_OF_VOICES);
  const sampleSpecs = useReturnedConst(getSampleSpecs, settings);
  for (let i = 0; i < voices.length; i++) {
    const exit = enterChildScope();
    const voice = voices[i];
    const { trigger, gate } = voice;
    const voiceGain = useEnvelope(Curve.Log2)(trigger, gate, attack, decay, sustain, release);

    multiply(voiceGain, gain, voiceGain);

    if (voice.alive && allEqual(voiceGain, 0)) {
      voice.alive = false;
    }

    if (!voice.alive) {
      exit();
      continue;
    }

    const sampleOutput = useStereoSignal();
    useMultiSampleVoice(sampleSpecs, voice, sampleOutput);

    multiply(sampleOutput[0], voiceGain, sampleOutput[0]);
    multiply(sampleOutput[1], voiceGain, sampleOutput[1]);
    
    add(output[0], sampleOutput[0], output[0]);
    add(output[1], sampleOutput[1], output[1]);

    exit();
  }
};
const constants = {};
const sampleTuneInputs = {};
const keyColumns = [];

const pianoPreset = {
  sample1Loud: "0a0b7543-5ad5-40cf-931b-f3e549900ad9",
  sample1MedLoud: "67f234b9-390f-4c4e-b8b8-84a037c85bfd",
  sample1MedSoft: "3149599c-2e18-46a6-8e01-8a292a0b103d",
  sample1Soft: "aac6d925-9e72-4aba-9d15-d0b782c33879",
  sample2Loud: "7dff6a1d-2594-4192-b491-bcdd5324bc56",
  sample2MedLoud: "9b37821e-2f9e-4470-9439-90fd4768543d",
  sample2MedSoft: "59405360-922e-4adc-927a-8c20911b6e1b",
  sample2Soft: "2489aa4b-edd2-4401-9dcb-d7e64192b9f7",
  sample3Loud: "9810de19-6cc9-448c-a877-712c75aab6ae",
  sample3MedLoud: "5733fa2f-335c-4f5d-beda-9ff3145a7340",
  sample3MedSoft: "f6060279-1014-462c-9bb9-2042b8e058af",
  sample3Soft: "c4525e69-36a9-4393-8463-9f3737db5927",
  sample4Loud: "9269aeb1-ce57-41ec-917f-c07e1c244c6f",
  sample4MedLoud: "8b791828-aa88-4261-9de6-db1c741a9f6c",
  sample4MedSoft: "7157adb8-dfbd-4ddb-a53f-550f7795db9b",
  sample4Soft: "6a6a77b0-5a8c-4a87-bbf1-65d3528b29d4",
}

keys.forEach((k) => {
  const currentColumn = [];
  constants[`sample${k}Pitch`] = numberConstant(`${k} Pitch`, { defaultValue: k * 24, min: 0, max: 127 });
  velsReversed.forEach((v) => {
    const constantKey = `sample${k}${v}`;
    constants[constantKey] = audioBufferConstant(`${k} (${v})`, pianoPreset[constantKey]);
    sampleTuneInputs[`sample${k}${v}Tune`] = numberSetting(`Sample ${k} ${v} Tune`, { defaultValue: 0, max: 1, min: -1 });

    currentColumn.push(
      altKnob(
        `sample${k}${v}Tune`,
        'Tune',
        titled.audioBufferPicker(`sample${k}${v}`, `${k} (${v})`)
      )
    );
  });
  currentColumn.push(titled.number(`sample${k}Pitch`));
  keyColumns.push(col(currentColumn));
});

const velColumn = [];
velsReversed.forEach((v, i) => {
  constants[`${v}Velocity`] = numberConstant(`${v} Velocity`, {
    defaultValue: Math.round(127 * ((vels.length - 1 - i) / (vels.length - 1))),
    min: 0,
    max: 127,
  });
  velColumn.push(box(titled.number(`${v}Velocity`, `${v} Velocity`), 'auto', 110));
});
keys.slice(vels.length - 1).forEach((k) => {
  velColumn.push(spacer(100));
});

this.constructDevice = () =>
  constructPerBufferDevice({
    implementation: SimpleSynth,
    inputs: {
      input: midi('In'),
      attack: adsrTimeSetting('Attack', 1),
      decay: adsrTimeSetting('Decay', 100),
      sustain: zeroOneSetting('Sustain', 1),
      release: adsrTimeSetting('Release', 150),
      gain: dbSetting('Gain', { defaultValue: -9 }),
      ...sampleTuneInputs
    },
    constants,
    layout: col([
      row([
        col(velColumn),
        ...keyColumns,
        col([
          box(titled.knob('attack'), 'auto', 110),
          box(titled.knob('decay'), 'auto', 110),
          box(titled.knob('sustain'), 'auto', 110),
          box(titled.knob('release'), 'auto', 110),
          spacer(100)
        ]),
        col([
          box(titled.knob('gain'), 'auto', 100),
          spacer(430)
        ]),
      ]),
    ]),
    outputs: {
      output: stereo('Out'),
    },
    presetTags: {
      'Instrument': [
        'Bass',
        'Synth Lead',
        'Synth Pad',
        'Piano/Keys',
        'Percussion',
        'Plucked Strings',
        'Bowed Strings',
        'Brass',
        'Woodwinds',
        'Sound Effect',
      ],
      'Genre': [
        'Ambient',
        'Chill',
        'Dubstep',
        'EDM',
        'Funk',
        'Hip Hop',
        'House',
        'Jazz',
        'Lo-Fi',
        'Soundtrack',
        'Metal',
        'Pop',
        'R&B',
        'Rock',
        'Techno',
        'Trance',
      ],
      'Playback': [
        'Monophonic',
        'Polyphonic',
        'Portamento',
        'Arpeggiated',
        'Long/Evolving',
        'Chord',
        'Slow Attack',
        'Long Release'
      ]
    },
  });
