QIN is a digital instrument that gets inspiration from the traditional Chinese instrument, the guqin.
Guqin (literally "ancient instrument") is a plucked string instrument similar to a zither with more than two thousand years of history. Its sound has two key characteristics:
For reference, here is a YouTube video of playing Bach Cello Suite on the guqin:
QIN attempts to simulate these two characteristics of the guqin.
QIN is controlled using a joystick to simulate the plucking and a strip sensor to simulate the pressing of the string for pitch and aftertouch articulations.
A pluck is triggered when the joystick moves out and back to the natural position, and the maximum amplitude of offset before moving back is mapped to the velocity of the plucking. The position on the strip sensor is mapped to the pitch of the string, and the pressure on the strip sensor is mapped to the aftertouch intensity.
Since QIN supports polyphony, the aftertouch is applied to the latest played note.
QIN uses an Arduino to read the sensor values and send them to a computer running a Pure Data patch that generates the sound. A SoftPot Membrane Potentiometer and a Force Sensitive Resistor Sensor are used together to form the strip sensor.
The Pure Data patch implements a Karplus-Strong string synthesizer to generate the plucked string sound. The velocity value controls the amplitude of the initial signal. The aftertouch value controls the resonance parameter of the feedback loop.
#include <Arduino.h>
#define PIN_POTENTIOMETER A0
#define PIN_FSR A2
#define PIN_JOYSTICK_Y A5
#define Y0 512
#define Y_BASE 5
#define FREQ_LOW 28
#define FREQ_HIGH 1023
#define FSR_BASE 1004
int y, p, f;
int y_sign_last = 0;
int y_abs = 1;
int y_abs_max = 0;
void setup() {
Serial.begin(115200);
}
void loop() {
y = map(analogRead(PIN_JOYSTICK_Y), 0, 1023, 0, 1023) - Y0;
p = map(analogRead(PIN_POTENTIOMETER), 0, 1023, FREQ_LOW, FREQ_HIGH);
f = analogRead(PIN_FSR) - FSR_BASE;
// Denoise
if (y >= 0 && y < Y_BASE) {
y = 0;
}
if (f < 0) {
f = 0;
}
y_abs = abs(y);
if (y_abs > y_abs_max) {
y_abs_max = y_abs;
}
if (y_sign_last != 0 && y_sign_last * y <= 0) {
Serial.print("A");
Serial.print(" ");
Serial.print(p);
Serial.print(" ");
Serial.print(y_abs_max);
Serial.println();
y_abs_max = 0;
} else {
Serial.print("T");
Serial.print(" ");
Serial.print(p);
Serial.print(" ");
Serial.print(f);
Serial.println();
}
y_sign_last = y==0 ? 0 : (y > 0 ? 1 : -1);
delay(20);
}