Home記事一覧フォーラム

SynthesijerでSynthesizerを作ってみた

【更新履歴】
2017/10/26 新規公開


SynthesijerでSynthesizerを作ってみました。

ターゲットボードについて

このプロジェクトはFPGA開発ボード「Terasic DE0-CV」用です。

ハードウェア

●ジャイロセンサー・マウス







クリックして拡大

●音声出力インターフェイス

DE0-CV向けオーディオ・アダプタの製作と同じものです。DE0-CVのGPIOとの接続は単線のジャンパーワイヤで行います。

ビルド・実行方法

Synthesijerはあらかじめ「高位合成ツール「Synthesijer」を使う」の方法で、また、Quartus Primeは「AlteraのFPGA開発ツール「Quartus Prime」をUbuntuにインストールする」の方法でインストールしているものとします。

ダウンロード:pictheremin.tar.gz

tar xzf pictheremin.tar.gz

cd pictheremin/synthesijer

make

Quartus Prime Ver.15.0以上 でプロジェクトファイル pictheremin/de0-cv/de0_cv_start.qpf を開いて「Start Compilation」、「Programmer」で転送して実行します。

ソースコード

これらのソースコードはBSD 2-Clauseライセンスで公開します。

Pictheremin.java
/*
  Copyright (c) 2017, miya
  All rights reserved.

  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/


import synthesijer.lib.timer.*;
import synthesijer.lib.led.*;
import synthesijer.rt.*;

public class Pictheremin
{
  // COUNT = 50,000,000 Hz / 100,000 Hz / 4 = 125
  private static final int COUNT = 125;
  private static final int GYRO_ADDR = 0x6b;
  private static final int COMMAND = 0x00;
  private static final int DATA = 0x40;
  private static final int WIDTH = 640;
  private static final int HEIGHT = 480;
  private static final int SCREEN_SHIFT_BITS = 11;
  private static final int MAX_Z = (640 << SCREEN_SHIFT_BITS);
  private static final int MAX_X = (480 << SCREEN_SHIFT_BITS);

  private static final int FIXED_BITS = 15;
  private static final int FIXED_BITS_OCT = 24;
  private static final int FIXED_BITS_ENV = 8;
  private static final int SCALE_SIZE = 12;
  private static final int SCALE_DATA_SIZE = 16;
  private static final int FINE_SCALE_DATA_SIZE = 128;
  private static final int WAVE_BUFFER_BITS = 11;
  private static final int TUNE_A = ((WAVE_BUFFER_BITS - 1) * SCALE_SIZE * FINE_SCALE_DATA_SIZE);
  private static final int CONST01 = ((1 << FIXED_BITS_OCT) / SCALE_SIZE / FINE_SCALE_DATA_SIZE);
  private static final int CONST02 = (SCALE_SIZE * FINE_SCALE_DATA_SIZE);

  private final I2CIface i2c = new I2CIface();
  private final KeyInput key = new KeyInput();
  private final VideoController video = new VideoController();
  private final Synthesizer synth = new Synthesizer();

  private int dx = 0;
  private int dy = 0;
  private int dz = 0;

  private int min(int x, int y)
  {
    if (x > y)
    {
      return y;
    }
    else
    {
      return x;
    }
  }

  private int max(int x, int y)
  {
    if (x > y)
    {
      return x;
    }
    else
    {
      return y;
    }
  }

  private void resetSensor()
  {
    int i = 0;
    short ax, ay, az;
    short sx = 0;
    short sy = 0;
    short sz = 0;
    while (i < 256)
    {
      if ((i2c.i2cRead(GYRO_ADDR, 0x2f) & 0x20) != 0x20)
      {
        ax = (short)i2c.i2cRead(GYRO_ADDR, 0x28);
        ax |= (short)(i2c.i2cRead(GYRO_ADDR, 0x29) << 8);
        ay = (short)i2c.i2cRead(GYRO_ADDR, 0x2a);
        ay |= (short)(i2c.i2cRead(GYRO_ADDR, 0x2b) << 8);
        az = (short)i2c.i2cRead(GYRO_ADDR, 0x2c);
        az |= (short)(i2c.i2cRead(GYRO_ADDR, 0x2d) << 8);
        sx += ax;
        sy += ay;
        sz += az;
        i2c.cycleWait(200000);
        i++;
      }
    }
    dx = sx >> 8;
    dy = sy >> 8;
    dz = sz >> 8;
  }

  private void init()
  {
    video.start();
    synth.start();
    i2c.i2cInit(COUNT);
    i2c.cycleWait(50000000);

    // GYRO ON
    i2c.i2cWrite(GYRO_ADDR, 0x20, 0x3f); // enable xyz axis, maximum bandwidth
    i2c.i2cWrite(GYRO_ADDR, 0x2e, 0x40); // FIFO mode: stream
    i2c.i2cWrite(GYRO_ADDR, 0x24, 0x40); // FIFO enable
  }

  @auto
  public void main()
  {
    short ax = 0;
    short ay = 0;
    short az = 0;
    int x = 0;
    int y = 0;
    int z = 0;
    byte nkey = (byte)0;

    int debugCounter = 0;
    int debugRate = 95;
    int debug0 = 0;
    int debug1 = 0;

    init();
    resetSensor();

    while (true)
    {
      nkey = key.value;
      if ((nkey & 1) == 1)
      {
        synth.SynthNoteOn = true;
      }
      else
      {
        synth.SynthNoteOn = false;
      }

      if ((nkey & 2) == 2)
      {
      }

      if ((i2c.i2cRead(GYRO_ADDR, 0x2f) & 0x20) != 0x20)
      {
        ax = (short)i2c.i2cRead(GYRO_ADDR, 0x28);
        ax |= (short)(i2c.i2cRead(GYRO_ADDR, 0x29) << 8);
        ay = (short)i2c.i2cRead(GYRO_ADDR, 0x2a);
        ay |= (short)(i2c.i2cRead(GYRO_ADDR, 0x2b) << 8);
        az = (short)i2c.i2cRead(GYRO_ADDR, 0x2c);
        az |= (short)(i2c.i2cRead(GYRO_ADDR, 0x2d) << 8);
        x = x + (int)(ax - dx);
        y = y + (int)(ay - dy);
        z = z - (int)(az - dz);

        z = max(z, 0);
        z = min(z, MAX_Z);
        x = max(x, 0);
        x = min(x, MAX_X);
        int cursor_x = z >> SCREEN_SHIFT_BITS;
        int cursor_y = x >> SCREEN_SHIFT_BITS;
        int f1 = (z >>> 8) + TUNE_A;
        int oct = (f1 * CONST01) >>> FIXED_BITS_OCT;
        int f2 = f1 - (oct * CONST02);
        int note = f2 >>> 7;
        int fine = f2 & 0x7f;
        int vol = (MAX_X - x) >> 4;

        synth.SynthNote = note;
        synth.SynthFine = fine;
        synth.SynthOct = oct - 1;
        synth.SynthVol = vol;

        video.setParam(cursor_x, cursor_y, nkey);

        debugCounter++;
        if (debugCounter == debugRate)
        {
          debugCounter = 0;
          debug0 = az;
          debug1 = ax;
        }

        video.debug[0] = cursor_x;
        video.debug[1] = cursor_y;
        video.debug[2] = dz;
        video.debug[3] = dx;
      }

      i2c.cycleWait(100000);
    }
  }
}
Synthesizer.java
/*
  Copyright (c) 2017, miya
  All rights reserved.

  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/



public class Synthesizer extends Thread
{
  private static final int INT_BITS = 32;
  private static final int FIXED_BITS = 15;
  private static final int FIXED_BITS_ENV = 8;
  private static final int FIXED_BITS_OCT = 24;
  private static final int FIXED_SCALE = (1 << FIXED_BITS);
  private static final int SCALE_SIZE = 12;
  private static final int SCALE_DATA_SIZE = 16;
  private static final int FINE_SCALE_DATA_SIZE = 128;

  private static final int WAVE_BUFFER_BITS = 11;
  private static final int WAVE_BUFFER_SIZE = (1 << WAVE_BUFFER_BITS);
  private static final int ECHO_BUFFER_SIZE_L = 0x4000;
  private static final int ECHO_BUFFER_SIZE_R = 0x4000;
  private static final int ECHO_BUFFER_SIZE_L_M1 = (ECHO_BUFFER_SIZE_L - 1);
  private static final int ECHO_BUFFER_SIZE_R_M1 = (ECHO_BUFFER_SIZE_R - 1);
  private static final int TUNE_A = ((WAVE_BUFFER_BITS - 1) * SCALE_SIZE * FINE_SCALE_DATA_SIZE);
  private static final int OSCS_BITS = 2;
  private static final int OSCS = (1 << OSCS_BITS);
  private static final int CONST01 = ((1 << FIXED_BITS_OCT) / SCALE_SIZE / FINE_SCALE_DATA_SIZE);
  private static final int CONST02 = (SCALE_SIZE * FINE_SCALE_DATA_SIZE);
  private static final int CONST03 = (1 << FIXED_BITS << FIXED_BITS_ENV);
  private static final int CONST04 = (FIXED_SCALE / 2);
  private static final int CONST05 = (CONST04 / 2);
  private static final int PATCH_BITS = 1;
  private static final int PATCH_REAL_SIZE = 2;
  private static final int PATCH_DATA_SIZE = (1 << (PATCH_BITS + OSCS_BITS));

  private static final int PARAM_NAME_modLevel0 = 0;
  private static final int PARAM_NAME_mixOut = 1;
  private static final int PARAM_NAME_envelopeLevelA = 2;
  private static final int PARAM_NAME_envelopeLevelS = 3;
  private static final int PARAM_NAME_envelopeDiffA = 4;
  private static final int PARAM_NAME_envelopeDiffD = 5;
  private static final int PARAM_NAME_envelopeDiffR = 6;
  private static final int PARAM_NAME_levelL = 7;
  private static final int PARAM_NAME_levelR = 8;
  private static final int PARAM_NAME_levelRev = 9;

  private final int[] echoBufferL = new int[ECHO_BUFFER_SIZE_L];
  private final int[] echoBufferR = new int[ECHO_BUFFER_SIZE_R];
  private final int[] pitch = new int[OSCS];

  private final AudioOutputWrapper audio = new AudioOutputWrapper();
  private final Oscillator osc0 = new Oscillator();
  private final Oscillator osc1 = new Oscillator();
  private final Oscillator osc2 = new Oscillator();
  private final Oscillator osc3 = new Oscillator();

  private final ROMscaledata scaledata = new ROMscaledata();
  private final ROMfinescaledata finescaledata = new ROMfinescaledata();

  private final FreeRunCounter32Iface timer = new FreeRunCounter32Iface();
  public final int[] debug = new int[16];

  private int mixL;
  private int mixR;
  private int mixRevL;
  private int mixRevR;

  public int sample;
  public int SynthNote;
  public int SynthFine;
  public int SynthOct;
  public int SynthVol;
  public boolean SynthNoteOn;

  public void setParam(int osc, int name, int value)
  {
    switch (osc)
    {
      case 0: osc0.setParam(name, value); break;
      case 1: osc1.setParam(name, value); break;
      case 2: osc2.setParam(name, value); break;
      case 3: osc3.setParam(name, value); break;
    }
  }

  public void run()
  {
    boolean validToggle = false;
    int echoAddrL = 0;
    int echoAddrR = 0;
    boolean goToggle = false;

    // (18MHz / 48000Hz) - 1 = 374
    audio.clock_divider = 374;

    // tone parameter
    for (int i = 0; i < OSCS; i++)
    {
      setParam(i, PARAM_NAME_envelopeLevelA, CONST03);
      setParam(i, PARAM_NAME_envelopeLevelS, CONST03);
      setParam(i, PARAM_NAME_envelopeDiffA, CONST03 >> 9);
      setParam(i, PARAM_NAME_envelopeDiffD, (0 - CONST03) >> 16);
      setParam(i, PARAM_NAME_envelopeDiffR, (0 - CONST03) >> 12);
      setParam(i, PARAM_NAME_levelL, CONST05);
      setParam(i, PARAM_NAME_levelR, CONST05);
      setParam(i, PARAM_NAME_levelRev, CONST04);
      setParam(i, PARAM_NAME_mixOut, i & 1);
      setParam(i, PARAM_NAME_modLevel0, 0);
      pitch[i] = 0;
    }

    osc0.start();
    osc1.start();
    osc2.start();
    osc3.start();

    while (true)
    {
      timer.reset();

      osc0.goToggle = goToggle;
      osc1.goToggle = goToggle;
      osc2.goToggle = goToggle;
      osc3.goToggle = goToggle;

      while (true)
      {
        if ((osc0.outDoneToggle == goToggle) &&
            (osc1.outDoneToggle == goToggle) &&
            (osc2.outDoneToggle == goToggle) &&
            (osc3.outDoneToggle == goToggle))
        {
          break;
        }
      }

      mixL = osc0.outWaveL + osc1.outWaveL + osc2.outWaveL + osc3.outWaveL;
      mixR = osc0.outWaveR + osc1.outWaveR + osc2.outWaveR + osc3.outWaveR;
      mixRevL = osc0.outRevL + osc1.outRevL + osc2.outRevL + osc3.outRevL;
      mixRevR = osc0.outRevR + osc1.outRevR + osc2.outRevR + osc3.outRevR;
      osc0.pitch = pitch[0];
      osc1.pitch = pitch[1];
      osc2.pitch = pitch[2];
      osc3.pitch = pitch[3];
      osc0.velocity = SynthVol;
      osc1.velocity = SynthVol;
      osc2.velocity = SynthVol;
      osc3.velocity = SynthVol;
      osc0.noteOn = SynthNoteOn;
      osc1.noteOn = SynthNoteOn;
      osc2.noteOn = SynthNoteOn;
      osc3.noteOn = SynthNoteOn;

      // Connect Patch
      osc0.modPatch0 = 0;
      osc0.modPatch1 = 0;
      osc1.modPatch0 = 0;
      osc1.modPatch1 = osc0.outData;
      osc2.modPatch0 = 0;
      osc2.modPatch1 = 0;
      osc3.modPatch0 = 0;
      osc3.modPatch1 = osc2.outData;

      goToggle = !goToggle;

      for (int i = 0; i < OSCS; i++)
      {
        pitch[i] = (((scaledata.data[SynthNote] * finescaledata.data[SynthFine]) >>> FIXED_BITS) << (SynthOct + (i & 1))) + 234567 * i;
      }

      echoAddrL++;
      if (echoAddrL > 0x3fff)
      {
        echoAddrL = 0;
      }
      echoAddrR++;
      if (echoAddrR > 0x2e51)
      {
        echoAddrR = 0;
      }
      int current_rev_L = echoBufferL[echoAddrL];
      int current_rev_R = echoBufferR[echoAddrR];
      echoBufferL[echoAddrL] = mixRevR + (current_rev_R >> 1);
      echoBufferR[echoAddrR] = mixRevL + (current_rev_L >> 1);
      int waveL = (mixL + current_rev_L + 0x8000) & 0xffff;
      int waveR = (mixR + current_rev_R + 0x8000) & 0xffff;
      int data = (waveR << 16) | waveL;

      debug[0] = sample;
      debug[1] = timer.get();

      while (audio.full == true)
      {
        //yield();
      }
      validToggle = !validToggle;
      audio.data = data;

      audio.valid_toggle = validToggle;
      sample++;
    }
  }
}
Oscillator.java
/*
  Copyright (c) 2017, miya
  All rights reserved.

  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/


public class Oscillator extends Thread
{
  private static final int STATE_ATTACK = 0;
  private static final int STATE_DECAY = 1;
  private static final int STATE_SUSTAIN = 2;
  private static final int STATE_RELEASE = 3;
  private static final int STATE_SLEEP = 4;
  private static final int WAVE_BUFFER_BITS = 11;
  private static final int INT_BITS = 32;
  private static final int FIXED_BITS = 15;
  private static final int FIXED_BITS_ENV = 8;
  private static final int WAVE_ADDR_SHIFT = (INT_BITS - WAVE_BUFFER_BITS);
  private static final int WAVE_ADDR_SHIFT_M = (WAVE_ADDR_SHIFT - FIXED_BITS);
  private static final int FIXED_SCALE = (1 << FIXED_BITS);
  private static final int FIXED_SCALE_M1 = (FIXED_SCALE - 1);
  private static final int WAVE_BUFFER_SIZE = (1 << WAVE_BUFFER_BITS);
  private static final int WAVE_BUFFER_SIZE_M1 = (WAVE_BUFFER_SIZE - 1);

  private static final int PARAM_NAME_modLevel0 = 0;
  private static final int PARAM_NAME_mixOut = 1;
  private static final int PARAM_NAME_envelopeLevelA = 2;
  private static final int PARAM_NAME_envelopeLevelS = 3;
  private static final int PARAM_NAME_envelopeDiffA = 4;
  private static final int PARAM_NAME_envelopeDiffD = 5;
  private static final int PARAM_NAME_envelopeDiffR = 6;
  private static final int PARAM_NAME_levelL = 7;
  private static final int PARAM_NAME_levelR = 8;
  private static final int PARAM_NAME_levelRev = 9;

  private int modLevel0;
  private int mixOut;
  private int envelopeLevelA;
  private int envelopeLevelS;
  private int envelopeDiffA;
  private int envelopeDiffD;
  private int envelopeDiffR;
  private int levelL;
  private int levelR;
  private int levelRev;

  private int state;
  private int count;
  private int currentLevel;
  private boolean noteOnSave;

  public boolean noteOn;
  public boolean goToggle;
  public boolean outDoneToggle;
  public int pitch;
  public int velocity;
  public int modPatch0;
  public int modPatch1;
  public int outData;
  public int outWaveL;
  public int outWaveR;
  public int outRevL;
  public int outRevR;

  private final ROMsintable sinTable = new ROMsintable();

  public void setParam(int name, int value)
  {
    switch (name)
    {
      case PARAM_NAME_modLevel0: modLevel0 = value; break;
      case PARAM_NAME_mixOut: mixOut = value; break;
      case PARAM_NAME_envelopeLevelA: envelopeLevelA = value; break;
      case PARAM_NAME_envelopeLevelS: envelopeLevelS = value; break;
      case PARAM_NAME_envelopeDiffA: envelopeDiffA = value; break;
      case PARAM_NAME_envelopeDiffD: envelopeDiffD = value; break;
      case PARAM_NAME_envelopeDiffR: envelopeDiffR = value; break;
      case PARAM_NAME_levelL: levelL = value; break;
      case PARAM_NAME_levelR: levelR = value; break;
      case PARAM_NAME_levelRev: levelRev = value; break;
      default: break;
    }
  }

  public void run()
  {
    state = STATE_SLEEP;

    while (true)
    {
      while (goToggle == outDoneToggle)
      {
        //yield();
      }

      // envelope generator
      if ((noteOn == true) && (noteOnSave != noteOn))
      {
        state = STATE_ATTACK;
      }
      if ((noteOn == false) && (noteOnSave != noteOn))
      {
        state = STATE_RELEASE;
      }
      noteOnSave = noteOn;

      switch (state)
      {
        case STATE_ATTACK:
        {
          currentLevel += envelopeDiffA;
          if (currentLevel > envelopeLevelA)
          {
            currentLevel = envelopeLevelA;
            state = STATE_DECAY;
          }
          break;
        }

        case STATE_DECAY:
        {
          currentLevel += envelopeDiffD;
          if (currentLevel < envelopeLevelS)
          {
            currentLevel = envelopeLevelS;
            state = STATE_SUSTAIN;
          }
          break;
        }

        case STATE_SUSTAIN:
        {
          break;
        }

        case STATE_RELEASE:
        {
          currentLevel += envelopeDiffR;
          if (currentLevel < 0)
          {
            currentLevel = 0;
            state = STATE_SLEEP;
          }
          break;
        }

        // STATE_SLEEP
        default:
        {
          count = 0;
          break;
        }
      }

      int wave_addr = (count +
                       (modPatch0 * modLevel0) +
                       (modPatch1 * velocity)) >>> WAVE_ADDR_SHIFT_M;

      // fetch wave data
      int wave_addr_m = wave_addr & FIXED_SCALE_M1;
      int wave_addr_f = wave_addr >>> FIXED_BITS;
      int wave_addr_r = (wave_addr_f + 1) & WAVE_BUFFER_SIZE_M1;
      int osc_out_f = sinTable.data[wave_addr_f];
      int osc_out_r = sinTable.data[wave_addr_r];
      int osc_out = ((osc_out_f * (FIXED_SCALE - wave_addr_m)) >> FIXED_BITS) +
        ((osc_out_r * wave_addr_m) >> FIXED_BITS);
      outData = (osc_out * (currentLevel >> FIXED_BITS_ENV)) >> FIXED_BITS;
      count += pitch;

      // mix
      if (mixOut == 0)
      {
        outWaveL = 0;
        outWaveR = 0;
        outRevL = 0;
        outRevR = 0;
      }
      else
      {
        outWaveL = (outData * levelL) >> FIXED_BITS;
        outWaveR = (outData * levelR) >> FIXED_BITS;
        outRevL = (outWaveL * levelRev) >> FIXED_BITS;
        outRevR = (outWaveR * levelRev) >> FIXED_BITS;
      }

      outDoneToggle = goToggle;
    }
  }
}
VideoController.java
/*
  Copyright (c) 2017, miya
  All rights reserved.

  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/


import synthesijer.rt.*;

public class VideoController extends Thread
{
  private static final int WIDTH = 640;
  private static final int HEIGHT = 480;
  private static final int SCREEN_SIZE_BITS = 6;
  private static final int SCREEN_SIZE = (1 << SCREEN_SIZE_BITS);
  private static final int SPRITE_SIZE_BITS = 6;
  private static final int SPRITE_SIZE_BITS_X2 = (SPRITE_SIZE_BITS << 1);
  private static final int SPRITE_SIZE = (1 << SPRITE_SIZE_BITS);
  private static final int SPRITE_SIZE_M1 = (SPRITE_SIZE - 1);
  private static final int SPRITE_SIZE2 = (1 << SPRITE_SIZE_BITS_X2);
  private static final int PAINT_SIZE_BITS = 7;
  private static final int PAINT_SIZE_BITS_X2 = (PAINT_SIZE_BITS << 1);
  private static final int PAINT_SIZE2 = (1 << PAINT_SIZE_BITS_X2);
  private static final int PARTICLES_BITS = 5;
  private static final int PARTICLES = (1 << PARTICLES_BITS);

  private final byte HEX_0 = (byte)('0' - 32);
  private final byte HEX_1 = (byte)('1' - 32);
  private final byte HEX_2 = (byte)('2' - 32);
  private final byte HEX_3 = (byte)('3' - 32);
  private final byte HEX_4 = (byte)('4' - 32);
  private final byte HEX_5 = (byte)('5' - 32);
  private final byte HEX_6 = (byte)('6' - 32);
  private final byte HEX_7 = (byte)('7' - 32);
  private final byte HEX_8 = (byte)('8' - 32);
  private final byte HEX_9 = (byte)('9' - 32);
  private final byte HEX_A = (byte)('A' - 32);
  private final byte HEX_B = (byte)('B' - 32);
  private final byte HEX_C = (byte)('C' - 32);
  private final byte HEX_D = (byte)('D' - 32);
  private final byte HEX_E = (byte)('E' - 32);
  private final byte HEX_F = (byte)('F' - 32);

  private final VgaIface vga = new VgaIface();
  private final Sprite sp_cursor = new Sprite("SPRITE_SIZE_BITS", "6");
  private final Sprite sp_paint = new Sprite("SPRITE_SIZE_BITS", "7");
  private final Sprite sp_particle = new Sprite("SPRITE_SIZE_BITS", "7");
  private final ChrBG bg0 = new ChrBG("CHR_SIZE_BITS", "6", "BITMAP_BITS", "1");
  private final ROMdivisionsdata divdata = new ROMdivisionsdata();
  private final ROMchr chr = new ROMchr();

  private final int[] par_x = new int[PARTICLES];
  private final int[] par_y = new int[PARTICLES];
  private final int[] par_dx = new int[PARTICLES];
  private final int[] par_dy = new int[PARTICLES];
  private final int[] par_col = new int[PARTICLES];

  private int cursor_x = 0;
  private int cursor_y = 0;
  private int key = 0;
  private int chr_ptr = 0;
  private int par_time = 0;

  private int random = -59634649;

  public final int[] debug = new int[16];

  private int rand()
  {
    int r = random;
    r = r ^ (r << 13);
    r = r ^ (r >>> 17);
    r = r ^ (r << 5);
    random = r;
    return r;
  }

  private int min(int x, int y)
  {
    if (x > y)
    {
      return y;
    }
    else
    {
      return x;
    }
  }

  private int max(int x, int y)
  {
    if (x > y)
    {
      return x;
    }
    else
    {
      return y;
    }
  }

  private int hexConv(int value, int col)
  {
    byte c = (byte)((value >>> (col << 2)) & 0xf);
    int hex = '0';
    switch (c)
    {
      case 0x0: hex = HEX_0; break;
      case 0x1: hex = HEX_1; break;
      case 0x2: hex = HEX_2; break;
      case 0x3: hex = HEX_3; break;
      case 0x4: hex = HEX_4; break;
      case 0x5: hex = HEX_5; break;
      case 0x6: hex = HEX_6; break;
      case 0x7: hex = HEX_7; break;
      case 0x8: hex = HEX_8; break;
      case 0x9: hex = HEX_9; break;
      case 0xa: hex = HEX_A; break;
      case 0xb: hex = HEX_B; break;
      case 0xc: hex = HEX_C; break;
      case 0xd: hex = HEX_D; break;
      case 0xe: hex = HEX_E; break;
      case 0xf: hex = HEX_F; break;
    }
    return hex;
  }

  private void printValue(int value, int start_digit, int length, int color)
  {
    int i = start_digit;
    int j = start_digit - length;
    while (i > j)
    {
      bg0.chr[chr_ptr] = (byte)(color | hexConv(value, i));
      i--;
      chr_ptr++;
    }
  }

  public int getChrPos(int x, int y)
  {
    return x + (y << SCREEN_SIZE_BITS);
  }

  public int getPaintPos(int x, int y)
  {
    return (x >> 3) + ((y >> 3) << PAINT_SIZE_BITS);
  }

  public void setParam(int x, int y, int k)
  {
    cursor_x = x;
    cursor_y = y;
    key = k;
  }

  private void clear_screen()
  {
    for (int i = 0; i < PAINT_SIZE2; i++)
    {
      sp_paint.bitmap[i] = (byte)0;
    }
  }

  private void init()
  {
    sp_cursor.x = 0;
    sp_cursor.y = 0;
    sp_cursor.scale = 8;
    sp_paint.x = 0;
    sp_paint.y = 0;
    sp_paint.scale = 5;
    sp_particle.x = 0;
    sp_particle.y = 0;
    sp_particle.scale = 5;
    bg0.x = 0;
    bg0.y = 0;
    bg0.scale = 7;
    bg0.palette0 = 0xffff8300;
    bg0.palette1 = 0xffff1300;
    bg0.palette2 = 0xffff4800;
    bg0.palette3 = 0xffff6d00;

    for (int i = 0; i < 4096; i++)
    {
      bg0.bitmap[i] = chr.data[i];
    }

    // draw keybaord
    for (int i = 0; i < SCREEN_SIZE; i++)
    {
      for (int j = 0; j < SCREEN_SIZE; j++)
      {
        bg0.chr[(i << SCREEN_SIZE_BITS) + j] = divdata.data[j];
      }
    }

    for (int i = 0; i < PAINT_SIZE2; i++)
    {
      sp_particle.bitmap[i] = (byte)0;
    }

    for (int i = 0; i < SPRITE_SIZE2; i++)
    {
      sp_cursor.bitmap[i] = (byte)0;
    }

    for (int i = 0; i < SPRITE_SIZE_M1; i++)
    {
      int x = i + (((SPRITE_SIZE >> 1) - 1) << SPRITE_SIZE_BITS);
      int y = ((SPRITE_SIZE >> 1) - 1) + (i << SPRITE_SIZE_BITS);
      sp_cursor.bitmap[x] = (byte)255;
      sp_cursor.bitmap[y] = (byte)255;
    }

    for (int i = 0; i < PARTICLES; i++)
    {
      par_x[i] = 0;
      par_y[i] = 0;
      par_dx[i] = 0;
      par_dy[i] = 0;
      par_col[i] = (byte)0;
    }
  }

  public void run()
  {
    init();
    while (true)
    {
      // vsync wait
      while (vga.vsync == true)
      {
      }
      while (vga.vsync == false)
      {
      }

      // cursor
      sp_cursor.x = cursor_x - 31;
      sp_cursor.y = cursor_y - 31;

      // debug monitor
      chr_ptr = getChrPos(0, 29);
      for (int i = 0; i < 4; i++)
      {
        printValue(debug[i], 7, 8, 192);
        bg0.chr[chr_ptr] = (byte)192;
        chr_ptr++;
      }

      // paint
      if ((key & 1) == 1)
      {
        sp_paint.bitmap[getPaintPos(cursor_x, cursor_y)] = (byte)0x92;
      }

      if ((key & 2) == 2)
      {
        clear_screen();
      }

      // particles
      for (int i = 0; i < PARTICLES; i++)
      {
        int par_nx = par_x[i] + par_dx[i];
        int par_ny = par_y[i] + par_dy[i];
        int colr = (par_col[i] >>> 16) - 8;
        int colg = ((par_col[i] >>> 8) & 0xff) - 8;
        int colb = (par_col[i] & 0xff) - 8;
        colr = max(colr, 0);
        colg = max(colg, 0);
        colb = max(colb, 0);
        int par_ncol = (colr << 16) | (colg << 8) | colb;

        if (par_time == i)
        {
          par_nx = cursor_x;
          par_ny = cursor_y;
          int par_ndx = rand() & 7;
          int par_ndy = rand() & 7;
          int sw_dir = rand() & 3;
          switch (sw_dir)
          {
            case 0: par_ndx = -par_ndx; break;
            case 1: par_ndy = -par_ndy; break;
            case 2: par_ndx = -par_ndx; par_ndy = -par_ndy; break;
            default: break;
          }
          par_dx[i] = par_ndx;
          par_dy[i] = par_ndy;
          switch (rand() & 7)
          {
            case 0: par_ncol = 0x000000ff; break;
            case 1: par_ncol = 0x0000ff00; break;
            case 2: par_ncol = 0x0000ffff; break;
            case 3: par_ncol = 0x00ff0000; break;
            case 4: par_ncol = 0x00ff00ff; break;
            case 5: par_ncol = 0x00ffff00; break;
            case 6: par_ncol = 0x00ffffff; break;
            case 7: par_ncol = 0x00ffffff; break;
            default: break;
          }
        }

        byte par_ncol8 = (byte)(((par_ncol >>> 16) & 0xe0) | ((par_ncol >>> 11) & 0x1c) | ((par_ncol >>> 6) & 3));

        sp_particle.bitmap[getPaintPos(par_nx, par_ny)] = par_ncol8;
        sp_particle.bitmap[getPaintPos(par_x[i], par_y[i])] = (byte)0;
        par_x[i] = par_nx;
        par_y[i] = par_ny;
        par_col[i] = par_ncol;
      }
      par_time++;
      if (par_time > PARTICLES)
      {
        par_time = 0;
      }
    }
  }
}
I2CIface.java
/*
  Copyright (c) 2017, miya
  All rights reserved.

  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/


import synthesijer.lib.led.*;
import synthesijer.rt.*;

public class I2CIface
{
  public final FreeRunCounter32Iface counter = new FreeRunCounter32Iface();
  public final I2CWrapper i2c = new I2CWrapper();

  // COUNT = 50,000,000 Hz / 100,000 Hz / 4 = 125
  private int count;

  public void i2cInit(int divider)
  {
    count = divider;
    counter.reset();
    i2c.data_o = 0x03; // sda_o:1 scl_o:1
  }

  public int getCycle()
  {
    return counter.get();
  }

  public void cycleWait(int cycles)
  {
    counter.cycleWait(cycles);
  }

  public void i2cWait()
  {
    counter.cycleWait(count);
  }

  public void i2cStart()
  {
    i2c.data_o = 0x03; // sda_o:1 scl_o:1
    i2cWait();
    i2cWait();
    i2c.data_o = 0x01; // sda_o:0 scl_o:1
    i2cWait();
    i2c.data_o = 0x00; // sda_o:0 scl_o:0
    i2cWait();
  }

  private void i2cTxBit(int data)
  {
    int sda = data << 1;
    i2c.data_o = sda | 0; // sda_o:sda scl_o:0
    i2cWait();
    i2c.data_o = sda | 1; // sda_o:sda scl_o:1
    i2cWait();
    i2cWait();
    while ((i2c.data_i & 2) == 0)
    {
      // clock stretching
    }
    i2c.data_o = sda | 0; // sda_o:sda scl_o:0
    i2cWait();
  }

  private int i2cRxBit()
  {
    i2c.data_o = 0x02; // sda_o:1 scl_o:0
    i2cWait();
    i2c.data_o = 0x03; // sda_o:1 scl_o:1
    i2cWait();
    while ((i2c.data_i & 2) == 0)
    {
      // clock stretching
    }
    int data = i2c.data_i & 1;
    i2cWait();
    i2c.data_o = 0x02; // sda_o:1 scl_o:0
    i2cWait();
    return data;
  }

  public boolean i2cTx(int data)
  {
    boolean ack;

    for (int i = 0; i < 8; i++)
    {
      int sda = (data & 0x80) >>> 7;
      i2cTxBit(sda);
      data <<= 1;
    }

    if (i2cRxBit() == 0)
    {
      ack = true;
    }
    else
    {
      ack = false;
    }

    return ack;
  }

  public int i2cRx(boolean isAck)
  {
    int data = 0;

    for (int i = 0; i < 8; i++)
    {
      data <<= 1;
      data |= i2cRxBit();
    }

    if (isAck)
    {
      i2cTxBit(0);
    }
    else
    {
      i2cTxBit(1);
    }
    return data;
  }

  public void i2cStop()
  {
    i2c.data_o = 0x01; // sda_o:0 scl_o:1
    i2cWait();
    i2cWait();
    i2c.data_o = 0x03; // sda_o:1 scl_o:1
    i2cWait();
    i2cWait();
  }

  public void i2cWrite(int addr, int reg, int data)
  {
    i2cStart();
    i2cTx(addr << 1);
    i2cTx(reg);
    i2cTx(data);
    i2cStop();
  }

  public int i2cRead(int addr, int reg)
  {
    i2cStart();
    i2cTx(addr << 1);
    i2cTx(reg);
    i2cStart();
    i2cTx((addr << 1) | 1);
    int data = i2cRx(false);
    i2cStop();
    return data;
  }
}