<?xml version="1.0"?>
<!DOCTYPE ladspa SYSTEM "ladspa-swh.dtd">
<?xml-stylesheet href="ladspa.css" type="text/css"?>

<ladspa>
  <global>
    <meta name="maker" value="Steve Harris &lt;steve@plugin.org.uk&gt;"/>
    <meta name="copyright" value="GPL"/>
    <meta name="properties" value="HARD_RT_CAPABLE"/>
    <code><![CDATA[
      #include <stdlib.h>
      #include <limits.h>

      #include "ladspa-util.h"
      #include "util/biquad.h"

      #define BUF_LEN 0.1
      #define CLICK_BUF_SIZE 4096

      #define df(x) ((sinf(x) + 1.0f) * 0.5f)

      inline static float noise();
      inline static float noise()
      {
         static unsigned int randSeed = 23;
         randSeed = (randSeed * 196314165) + 907633515;
         return randSeed / (float)INT_MAX - 1.0f;
      }

    ]]></code>
  </global>

  <plugin label="vynil" id="1905" class="DistortionPlugin">
    <name>VyNil (Vinyl Effect)</name>

    <callback event="instantiate"><![CDATA[
      unsigned int i;
      unsigned int buffer_size;

      fs = (float)s_rate;
      buffer_size = 4096;
      while (buffer_size < s_rate * BUF_LEN) {
	buffer_size *= 2;
      }
      buffer_m = malloc(sizeof(LADSPA_Data) * buffer_size);
      buffer_s = malloc(sizeof(LADSPA_Data) * buffer_size);
      buffer_mask = buffer_size - 1;
      buffer_pos = 0;
      click_gain = 0;
      phi = 0.0f; /* Angular phase */

      click_buffer = malloc(sizeof(LADSPA_Data) * CLICK_BUF_SIZE);
      for (i=0; i<CLICK_BUF_SIZE; i++) {
	if (i<CLICK_BUF_SIZE / 2) {
	  click_buffer[i] = (double)i / (double)(CLICK_BUF_SIZE / 2);
	  click_buffer[i] *= click_buffer[i];
	  click_buffer[i] *= click_buffer[i];
	  click_buffer[i] *= click_buffer[i];
	} else {
	  click_buffer[i] = click_buffer[CLICK_BUF_SIZE - i];
	}
      }

      sample_cnt = 0;
      def = 0.0f;
      def_target = 0.0f;

      lowp_m = calloc(sizeof(biquad), 1);
      lowp_s = calloc(sizeof(biquad), 1);
      highp = calloc(sizeof(biquad), 1);
      noise_filt = calloc(sizeof(biquad), 1);
    ]]></callback>

    <callback event="activate"><![CDATA[
      memset(buffer_m, 0, sizeof(LADSPA_Data) * (buffer_mask + 1));
      memset(buffer_s, 0, sizeof(LADSPA_Data) * (buffer_mask + 1));
      buffer_pos = 0;
      click_buffer_pos.all = 0;
      click_buffer_omega.all = 0;
      click_gain = 0;
      phi = 0.0f;

      lp_set_params(lowp_m, 16000.0, 0.5, fs);
      lp_set_params(lowp_s, 16000.0, 0.5, fs);
      lp_set_params(highp, 10.0, 0.5, fs);
      lp_set_params(noise_filt, 1000.0, 0.5, fs);
    ]]></callback>

    <callback event="run"><![CDATA[
      unsigned long pos;
      float deflec = def;
      float deflec_target = def_target;
      float src_m, src_s;

      /* angular velocity of platter * 16 */
      const float omega = 960.0f / (rpm * fs);
      const float age = (2000 - year) * 0.01f;
      const unsigned int click_prob = (age*age*(float)RAND_MAX)/10 + click * 0.02 * RAND_MAX;
      const float noise_amp = (click + wear * 0.3f) * 0.12f + (1993.0f - year) * 0.0031f;
      const float bandwidth = (year - 1880.0f) * (rpm * 1.9f);
      const float noise_bandwidth = bandwidth * (0.25 - wear * 0.02) + click * 200.0 + 300.0;
      const float stereo = f_clamp((year - 1940.0f) * 0.02f, 0.0f, 1.0f);
      const float wrap_gain = age * 3.1f + 0.05f;
      const float wrap_bias = age * 0.1f;

      lp_set_params(lowp_m, bandwidth * (1.0 - wear * 0.86), 2.0, fs);
      lp_set_params(lowp_s, bandwidth * (1.0 - wear * 0.89), 2.0, fs);
      hp_set_params(highp, (2000-year) * 8.0, 1.5, fs);
      lp_set_params(noise_filt, noise_bandwidth, 4.0 + wear * 2.0, fs);

      for (pos = 0; pos < sample_count; pos++) {
	unsigned int o1, o2;
	float ofs;

	if ((sample_cnt & 15) == 0) {
	  const float ang = phi * 2.0f * M_PI;
	  const float w = warp * (2000.0f - year) * 0.01f;
	  deflec_target = w*df(ang)*0.5f + w*w*df(2.0f*ang)*0.31f +
                             w*w*w*df(3.0f*ang)*0.129f;
	  phi += omega;
	  while (phi > 1.0f) {
	    phi -= 1.0f;
	  }
	  if ((unsigned int)rand() < click_prob) {
	    click_buffer_omega.all = ((rand() >> 6) + 1000) * rpm;
	    click_gain = noise_amp * 5.0f * noise();
	  }
	}
	deflec = deflec * 0.1f + deflec_target * 0.9f;

	/* matrix into mid_side representation (this is roughly what stereo
         * LPs do) */
	buffer_m[buffer_pos] = in_l[pos] + in_r[pos];
	buffer_s[buffer_pos] = in_l[pos] - in_r[pos];

	/* cacluate the effects of the surface warping */
	ofs = fs * 0.009f * deflec;
	o1 = f_round(floorf(ofs));
	o2 = f_round(ceilf(ofs));
	ofs -= o1;
	src_m = LIN_INTERP(ofs, buffer_m[(buffer_pos - o1 - 1) & buffer_mask], buffer_m[(buffer_pos - o2 - 1) & buffer_mask]);
	src_s = LIN_INTERP(ofs, buffer_s[(buffer_pos - o1 - 1) & buffer_mask], buffer_s[(buffer_pos - o2 - 1) & buffer_mask]);

	src_m = biquad_run(lowp_m, src_m + click_buffer[click_buffer_pos.part.in & (CLICK_BUF_SIZE - 1)] * click_gain);

	/* waveshaper */
	src_m = LIN_INTERP(age, src_m, sinf(src_m * wrap_gain + wrap_bias));

	/* output highpass */
	src_m = biquad_run(highp, src_m) + biquad_run(noise_filt, noise()) * noise_amp + click_buffer[click_buffer_pos.part.in & (CLICK_BUF_SIZE - 1)] * click_gain * 0.5f;

	/* stereo seperation filter */
	src_s = biquad_run(lowp_s, src_s) * stereo;

        buffer_write(out_l[pos], (src_s + src_m) * 0.5f);
        buffer_write(out_r[pos], (src_m - src_s) * 0.5f);

	/* roll buffer indexes */
	buffer_pos = (buffer_pos + 1) & buffer_mask;
	click_buffer_pos.all += click_buffer_omega.all;
	if (click_buffer_pos.part.in >= CLICK_BUF_SIZE) {
	  click_buffer_pos.all = 0;
	  click_buffer_omega.all = 0;
	}
	sample_cnt++;
      }

      plugin_data->buffer_pos = buffer_pos;
      plugin_data->click_buffer_pos = click_buffer_pos;
      plugin_data->click_buffer_omega = click_buffer_omega;
      plugin_data->click_gain = click_gain;
      plugin_data->sample_cnt = sample_cnt;
      plugin_data->def_target = deflec_target;
      plugin_data->def = deflec;
      plugin_data->phi = phi;
    ]]></callback>

    <callback event="cleanup"><![CDATA[
      free(plugin_data->buffer_m);
      free(plugin_data->buffer_s);
      free(plugin_data->click_buffer);
      free(plugin_data->lowp_m);
      free(plugin_data->lowp_s);
      free(plugin_data->noise_filt);
    ]]></callback>

    <port label="year" dir="input" type="control" hint="default_maximum">
      <name>Year</name>
      <p>The date of the recording/playback equipment to be simulated.</p>
      <range min="1900" max="1990"/>
    </port>

    <port label="rpm" dir="input" type="control" hint="default_minimum">
      <name>RPM</name>
      <p>The rotational speed of the platter.</p>
      <range min="33" max="78"/>
    </port>

    <port label="warp" dir="input" type="control" hint="default_0">
      <name>Surface warping</name>
      <p>The degree of variation in height of the record surface.</p>
      <range min="0.0" max="1.0"/>
    </port>

    <port label="click" dir="input" type="control" hint="default_0">
      <name>Crackle</name>
      <p>The number of scratches on the record surface.</p>
      <range min="0.0" max="1.0"/>
    </port>

    <port label="wear" dir="input" type="control" hint="default_0">
      <name>Wear</name>
      <p>The ammount of wear on the grooves.</p>
      <range min="0.0" max="1.0"/>
    </port>

    <port label="in_l" dir="input" type="audio">
      <name>Input L</name>
    </port>
    <port label="in_r" dir="input" type="audio">
      <name>Input R</name>
    </port>

    <port label="out_l" dir="output" type="audio">
      <name>Output L</name>
    </port>
    <port label="out_r" dir="output" type="audio">
      <name>Output R</name>
    </port>

    <instance-data label="fs" type="float" />
    <instance-data label="buffer_m" type="LADSPA_Data *" />
    <instance-data label="buffer_s" type="LADSPA_Data *" />
    <instance-data label="buffer_mask" type="unsigned int" />
    <instance-data label="buffer_pos" type="unsigned int" />

    <instance-data label="click_buffer" type="LADSPA_Data *" />
    <instance-data label="click_buffer_pos" type="fixp16" />
    <instance-data label="click_buffer_omega" type="fixp16" />
    <instance-data label="click_gain" type="float" />

    <instance-data label="phi" type="float" />
    <instance-data label="sample_cnt" type="unsigned int" />
    <instance-data label="def" type="float" />
    <instance-data label="def_target" type="float" />

    <instance-data label="lowp_m" type="biquad *" />
    <instance-data label="lowp_s" type="biquad *" />
    <instance-data label="noise_filt" type="biquad *" />
    <instance-data label="highp" type="biquad *" />
  </plugin>
</ladspa>

