4

It is easy to create in iOS application. Any one tell me the easy way to create the Sinwave animation while record and play the audio. I was tried soo many ways but I couldn't. Is there any third party frameworks for that?

Can any one help me out please..

Surezz
  • 561
  • 4
  • 12

2 Answers2

19

Use the below code to get the waveform like ios... get the amplitude from the recorder and pass to updateAmplitude() function to get the variation in voice.

public class WaveFormView extends View {

      private static final float defaultFrequency          = 1.5f;
      private static final float defaultAmplitude          = 1.0f;
      private static final float defaultIdleAmplitude      = 0.01f;
      private static final float defaultNumberOfWaves      = 5.0f;
      private static final float defaultPhaseShift         = -0.15f;
      private static final float defaultDensity            = 5.0f;
      private static final float defaultPrimaryLineWidth   = 3.0f;
      private static final float defaultSecondaryLineWidth = 1.0f;

      private float phase;
      private float amplitude;
      private float frequency;
      private float idleAmplitude;
      private float numberOfWaves;
      private float phaseShift;
      private float density;
      private float primaryWaveLineWidth;
      private float secondaryWaveLineWidth;
      Paint mPaintColor;
      Rect rect;
      boolean isStraightLine = false;

      public WaveFormView(Context context) {
          super(context);
          setUp();
      }

      public WaveFormView(Context context, AttributeSet attrs) {
          super(context, attrs);
          setUp();
      }

      public WaveFormView(Context context, AttributeSet attrs, int defStyleAttr) {
          super(context, attrs, defStyleAttr);
          setUp();
      }

      @TargetApi(Build.VERSION_CODES.LOLLIPOP)
      public WaveFormView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
          super(context, attrs, defStyleAttr, defStyleRes);
          setUp();
      }

      private void setUp() {
          this.frequency = defaultFrequency;

          this.amplitude = defaultAmplitude;
          this.idleAmplitude = defaultIdleAmplitude;

          this.numberOfWaves = defaultNumberOfWaves;
          this.phaseShift = defaultPhaseShift;
          this.density = defaultDensity;

          this.primaryWaveLineWidth = defaultPrimaryLineWidth;
          this.secondaryWaveLineWidth = defaultSecondaryLineWidth;
          mPaintColor = new Paint();
          mPaintColor.setColor(Color.WHITE);
      }

      public void updateAmplitude(float ampli, boolean isSpeaking) {
          this.amplitude = Math.max(ampli, idleAmplitude);
          isStraightLine = isSpeaking;
      }


      @Override
      protected void onDraw(Canvas canvas) {
          rect = new Rect(0,0,canvas.getWidth(),canvas.getWidth());
          canvas.drawColor(Color.BLUE);
          /*canvas.drawRect(rect, mPaintColor);*/
          if(isStraightLine) {
              for (int i = 0; i < numberOfWaves; i++) {
                  mPaintColor.setStrokeWidth(i == 0 ? primaryWaveLineWidth : secondaryWaveLineWidth);
                  float halfHeight = canvas.getHeight() / 2;
                  float width = canvas.getWidth();
                  float mid = canvas.getWidth() / 2;

                  float maxAmplitude = halfHeight - 4.0f;
                  float progress = 1.0f - (float) i / this.numberOfWaves;
                  float normedAmplitude = (1.5f * progress - 0.5f) * this.amplitude;
                  Path path = new Path();

                  float multiplier = Math.min(1.0f, (progress / 3.0f * 2.0f) + (1.0f / 3.0f));

                  for (float x = 0; x < width + density; x += density) {
                      // We use a parable to scale the sinus wave, that has its peak in the middle of the view.
                      float scaling = (float) (-Math.pow(1 / mid * (x - mid), 2) + 1);

                      float y = (float) (scaling * maxAmplitude * normedAmplitude * Math.sin(2 * Math.PI * (x / width) * frequency + phase) + halfHeight);

                      if (x == 0) {
                          path.moveTo(x, y);
                      } else {
                          path.lineTo(x, y);
                      }
                  }
                  mPaintColor.setStyle(Paint.Style.STROKE);
                  mPaintColor.setAntiAlias(true);
                  canvas.drawPath(path, mPaintColor);

              }
          } else {
              canvas.drawLine(5,canvas.getHeight()/2,canvas.getWidth(),canvas.getHeight()/2,mPaintColor );
              canvas.drawLine(0,canvas.getHeight()/2,canvas.getWidth(),canvas.getHeight()/2,mPaintColor);
              canvas.drawLine(-5, canvas.getHeight() / 2, canvas.getWidth(), canvas.getHeight()/2,mPaintColor );
          }
          this.phase += phaseShift;
          invalidate();
      }
}
Muthukrishnan Rajendran
  • 11,122
  • 3
  • 31
  • 41
Vennila
  • 240
  • 1
  • 11
4

Based on @Vennila answer.

Here is Kotlin version of WaveFormView.

class SiriVisualView(context: Context, attrs: AttributeSet) : View(context, attrs) {

    private var phase = 0f
    private var amplitude = 1.5f // wave height
    private var frequency = 1.2f // number of waves
    private var idleAmplitude = 0.05f // default height
    private var numberOfWaves = 8.0f // number of wave lines
    private var phaseShift = -0.1f // wave speed
    private var primaryWaveLineWidth = 2.0f // outer line stroke
    private var secondaryWaveLineWidth = 0.5f // inner line stroke
    private var density = 5f
    var mPaintColor: Paint = Paint()
    var isStraightLine = false

    fun updateViewColor(@ColorInt color: Int) {
        mPaintColor.color = color
    }

    fun updateSpeaking(isSpeaking: Boolean) {
        isStraightLine = isSpeaking
    }

    fun updateAmplitude(ampli: Float) {
        amplitude = Math.max(ampli, idleAmplitude)
    }

    fun updateSpeed(phase: Float) {
        phaseShift = phase
    }

    /** Here you can override default wave values and customize SiriVisualView

        updateNumberOfWaves()
        updatePrimaryLineStroke()
            . . .
    */

    override fun onDraw(canvas: Canvas) {
        if (isStraightLine) {
            var i = 0
            while (i < numberOfWaves) {
                mPaintColor.strokeWidth = if (i == 0) primaryWaveLineWidth else secondaryWaveLineWidth
                val halfHeight = height / 2.toFloat()
                val width = width.toFloat()
                val mid = width / 2.toFloat()
                val maxAmplitude = halfHeight - 4.0f
                val progress = 1.0f - i.toFloat() / numberOfWaves
                val normedAmplitude = (1.5f * progress - 0.5f) * amplitude
                val path = Path()
                val multiplier = Math.min(1.0f, progress / 3.0f * 2.0f + 1.0f / 3.0f)
                var x = 0f
                while (x < width + density) {
                    // We use a parable to scale the sinus wave, that has its peak in the middle of the view.
                    val scaling = (-Math.pow(
                        1 / mid * (x - mid).toDouble(),
                        2.0
                    ) + 1).toFloat()
                    val y = (scaling * maxAmplitude * normedAmplitude * Math.sin(2 * Math.PI * (x / width) * frequency + phase) + halfHeight).toFloat()
                    if (x == 0f) {
                        path.moveTo(x, y)
                    } else {
                        path.lineTo(x, y)
                    }
                    x += density
                }
                mPaintColor.style = Paint.Style.STROKE
                mPaintColor.isAntiAlias = true
                canvas.drawPath(path, mPaintColor)
                i++
            }
        } else {
            for(i in 5 downTo -5 step 5) {
                canvas.drawLine(
                    i.toFloat(),
                    height / 2.toFloat(),
                    width.toFloat(),
                    height / 2.toFloat(),
                    mPaintColor
                )
            }
        }
        phase += phaseShift
        invalidate()
    }
}

Add view to your XML file

<linc.com.visualtest.SiriVisualView
    android:id="@+id/siriView"
    android:layout_width="0dp"
    android:layout_height="200dp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintEnd_toEndOf="parent"/>

Update and customize view inside your Activity or Fragment

siriView.apply {
        updateSpeaking(true)
        updateViewColor(Color.WHITE)
        updateAmplitude(0.5f)
        updateSpeed(-0.1f)
    }

Here some screenshots of the SiriVisualView

enter image description here

enter image description here

enter image description here

lincollincol
  • 762
  • 2
  • 12
  • 23