Add resonant filter to C++ synth

This change adds a (single) resonant filter to the C++ synth unit, and
wires up both USB MIDI and on-screen controls for cutoff and resonance.

Also fixes a bug in KnobView which caused the knob value to jump around.
bklimt
Raph Levien 13 years ago
parent 8fcd77a762
commit ee72c697c1
  1. 69
      android/res/layout/piano2.xml
  2. 1
      android/res/values/strings.xml
  3. 28
      android/src/com/google/synthesizer/android/ui/PianoActivity2.java
  4. 4
      android/src/com/google/synthesizer/android/widgets/knob/KnobView.java
  5. 6
      cpp/src/module.h
  6. 13
      cpp/src/resofilter.cc
  7. 7
      cpp/src/resofilter.h
  8. 19
      cpp/src/synth_unit.cc
  9. 6
      cpp/src/synth_unit.h

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<TableLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.google.synthesizer"
android:id="@+id/TableLayout01"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:stretchColumns="*"
android:shrinkColumns="*">
<TableRow>
<TextView
android:text="@string/cutoff"
android:id="@+id/cutoffLabel"
android:gravity="center_horizontal" />
<TextView
android:text="@+string/resonance"
android:id="@+id/resonanceLabel"
android:gravity="center_horizontal" />
<TextView
android:text=""
android:id="@+id/emptyLabel"
android:gravity="center_horizontal" />
<TextView
android:text=""
android:id="@+id/emptyLabel"
android:gravity="center_horizontal" />
<TextView
android:text=""
android:id="@+id/emptyLabel"
android:gravity="center_horizontal" />
<TextView
android:text=""
android:id="@+id/emptyLabel"
android:gravity="center_horizontal" />
</TableRow>
<TableRow>
<com.google.synthesizer.android.widgets.knob.KnobView
android:id="@+id/cutoffKnob"
app:value="1.0"
app:min="0"
app:max="1"
android:layout_margin="2px" />
<com.google.synthesizer.android.widgets.knob.KnobView
android:id="@+id/resonanceKnob"
app:value="0.0"
app:min="0"
app:max="1"
android:layout_margin="2px" />
<Spinner
android:id="@+id/presetSpinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_span="4" />
</TableRow>
<TableRow>
<LinearLayout
android:layout_height="fill_parent"
android:layout_span="6"
android:orientation="vertical">
<com.google.synthesizer.android.widgets.piano.PianoView
android:id="@+id/piano"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
app:octaves="2"
app:first_octave="4" />
</LinearLayout>
</TableRow>
</TableLayout>

@ -47,6 +47,7 @@
<!-- Low Pass Filter -->
<string name="cutoff">Cutoff</string>
<string name="resonance">Resonance</string>
<!-- Amp -->
<string name="attack">Attack</string>

@ -39,10 +39,10 @@ import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.ToggleButton;
import com.google.synthesizer.R;
import com.google.synthesizer.android.AndroidGlue;
import com.google.synthesizer.android.widgets.knob.KnobListener;
import com.google.synthesizer.android.widgets.knob.KnobView;
import com.google.synthesizer.android.widgets.piano.PianoView;
@ -57,10 +57,11 @@ public class PianoActivity2 extends Activity {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN |
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setContentView(R.layout.piano);
setContentView(R.layout.piano2);
piano_ = (PianoView)findViewById(R.id.piano);
volumeKnob_ = (KnobView)findViewById(R.id.volumeKnob);
cutoffKnob_ = (KnobView)findViewById(R.id.cutoffKnob);
resonanceKnob_ = (KnobView)findViewById(R.id.resonanceKnob);
presetSpinner_ = (Spinner)findViewById(R.id.presetSpinner);
androidGlue_ = new AndroidGlue();
@ -89,6 +90,20 @@ public class PianoActivity2 extends Activity {
public void onNothingSelected(AdapterView<?> parent) {
}
});
cutoffKnob_.setKnobListener(new KnobListener() {
public void onKnobChanged(double newValue) {
int value = (int)Math.round(newValue * 127);
androidGlue_.onController(0, 1, value);
}
});
resonanceKnob_.setKnobListener(new KnobListener() {
public void onKnobChanged(double newValue) {
int value = (int)Math.round(newValue * 127);
androidGlue_.onController(0, 2, value);
}
});
piano_.bindTo(androidGlue_);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
tryConnectUsb();
@ -134,8 +149,8 @@ public class PianoActivity2 extends Activity {
for (int i = 0; i < nBytes; i += 4) {
int codeIndexNumber = buf[i] & 0xf;
int payloadBytes = 0;
if (codeIndexNumber == 8 || codeIndexNumber == 9) {
// TODO: pitchbend, control, etc
if (codeIndexNumber == 8 || codeIndexNumber == 9 || codeIndexNumber == 11 ||
codeIndexNumber == 14) {
payloadBytes = 3;
} else if (codeIndexNumber == 12) {
payloadBytes = 2;
@ -173,6 +188,7 @@ public class PianoActivity2 extends Activity {
private AndroidGlue androidGlue_;
private PianoView piano_;
private KnobView volumeKnob_;
private KnobView cutoffKnob_;
private KnobView resonanceKnob_;
private Spinner presetSpinner_;
}

@ -74,7 +74,7 @@ public class KnobView extends View {
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_POINTER_DOWN: {
case MotionEvent.ACTION_DOWN: {
// Just record the current finger position.
getDrawingRect(rect_);
previousX_ = event.getX() - rect_.centerX();
@ -116,7 +116,7 @@ public class KnobView extends View {
break;
}
case MotionEvent.ACTION_POINTER_UP: {
case MotionEvent.ACTION_UP: {
break;
}
}

@ -14,6 +14,9 @@
* limitations under the License.
*/
#ifndef SYNTH_MODULE_H
#define SYNTH_MODULE_H
#include <stdint.h>
class Module {
@ -23,3 +26,6 @@ class Module {
virtual void process(const int32_t **inbufs, const int32_t *control_in,
const int32_t *control_last, int32_t **outbufs) = 0;
};
#endif // SYNTH_MODULE_H

@ -14,7 +14,8 @@
* limitations under the License.
*/
#include "module.h"
#include "synth.h"
#include "freqlut.h"
#include "resofilter.h"
double this_sample_rate;
@ -30,8 +31,8 @@ ResoFilter::ResoFilter() {
}
int32_t compute_alpha(int32_t logf) {
// TODO
return 1 << 21;
// TODO: better tuning
return min(1 << 24, Freqlut::lookup(logf));
}
void ResoFilter::process(const int32_t **inbufs, const int32_t *control_in,
@ -42,6 +43,12 @@ void ResoFilter::process(const int32_t **inbufs, const int32_t *control_in,
int32_t k = control_last[1];
int32_t k_in = control_in[1];
int32_t delta_k = (k_in - k) >> lg_n;
if ((((int64_t)alpha_in * (int64_t)k_in) >> 24) > 1 << 24) {
k_in = ((1 << 30) / alpha_in) << 18;
}
if ((((int64_t)alpha * (int64_t)k) >> 24) > 1 << 24) {
k = ((1 << 30) / alpha) << 18;
}
const int32_t *ibuf = inbufs[0];
int32_t *obuf = outbufs[0];
int x0 = x[0];

@ -14,6 +14,11 @@
* limitations under the License.
*/
#ifndef SYNTH_RESOFILTER_H_
#define SYNTH_RESOFILTER_H_
#include "module.h"
class ResoFilter : Module {
public:
ResoFilter();
@ -25,3 +30,5 @@ class ResoFilter : Module {
private:
int32_t x[4];
};
#endif // SYNTH_RESOFILTER_H_

@ -44,6 +44,8 @@ SynthUnit::SynthUnit(RingBuffer *ring_buffer) {
memcpy(patch_data_, epiano, sizeof(epiano));
current_patch_ = 0;
current_note_ = 0;
filter_control_[0] = 258847126;
filter_control_[1] = 0;
}
// Transfer as many bytes as possible from ring buffer to input buffer.
@ -97,6 +99,17 @@ int SynthUnit::ProcessMidiMessage(const uint8_t *buf, int buf_size) {
return 3;
}
return 0;
} else if (cmd_type == 0xb0) {
if (buf_size >= 3) {
int controller = buf[1];
int value = buf[2];
if (controller == 1) {
filter_control_[0] = 129423563 + value * 1019083;
} else if (controller == 2) {
filter_control_[1] = value * 528416;
}
return 3;
} return 0;
} else if (cmd_type == 0xc0) {
if (buf_size >= 2) {
// program change
@ -149,6 +162,7 @@ void SynthUnit::GetSamples(int n_samples, int16_t *buffer) {
for (int i = 0; i < n_samples; i += N) {
int32_t audiobuf[N];
int32_t audiobuf2[N];
for (int j = 0; j < N; ++j) {
audiobuf[j] = 0;
}
@ -157,8 +171,11 @@ void SynthUnit::GetSamples(int n_samples, int16_t *buffer) {
active_note_[note].dx7_note->compute(audiobuf);
}
}
const int32_t *bufs[] = { audiobuf };
int32_t *bufs2[] = { audiobuf2 };
filter_.process(bufs, filter_control_, filter_control_, bufs2);
for (int j = 0; j < N; ++j) {
int32_t val = audiobuf[j] >> 4;
int32_t val = audiobuf2[j] >> 4;
int clip_val = val < -(1 << 24) ? 0x8000 : val >= (1 << 24) ? 0x7fff :
val >> 9;
// TODO: maybe some dithering?

@ -16,6 +16,7 @@
#include "dx7note.h"
#include "ringbuffer.h"
#include "resofilter.h"
struct ActiveNote {
int midi_note;
@ -35,7 +36,7 @@ class SynthUnit {
int ProcessMidiMessage(const uint8_t *buf, int buf_size);
RingBuffer *ring_buffer_;
static const int max_active_notes = 16;
static const int max_active_notes = 8;
ActiveNote active_note_[max_active_notes];
int current_note_;
uint8_t input_buffer_[8192];
@ -43,4 +44,7 @@ class SynthUnit {
uint8_t patch_data_[4096];
int current_patch_;
ResoFilter filter_;
int32_t filter_control_[2];
};

Loading…
Cancel
Save