Hi,
I am sonifying the touch location on the Trill Ring using map() so that a frequency-modulated tone is produced by one trip around the Ring. Specifically, I am computing y-coordinates from the touch location, and mapping the y-coordinates onto pitches within a fixed frequency range.
When I plot the y-coordinates (top right) below, I see the expected shape, but with unexpected discontinuities:
I think that these discontinuities arise from a sampling / resolution issue. If you look at the original touch locations (top left), you can see that they change values in a stepwise function and the steps are not of equal length (and the duration of each step is quite long, i.e. > 30ms). The bottom plot zooms in on a segment of touch locations, and you can see the issue clearly here.
I am using an autoscan interval of 1 (1 clock tick at 32 kHz), and have set the Trill readings to ultra fast mode (setScanSettings(0,9)). My code is printed below.
Your input on suspected sources of this problem are much appreciated!
Best,
Anna
/*
____ _____ _ _
| __ )| ____| | / \
| _ \| _| | | / _ \
| |_) | |___| |___ / ___ \
|____/|_____|_____/_/ \_\
The platform for ultra-low latency audio and sensor processing
http://bela.io
A project of the Augmented Instruments Laboratory within the
Centre for Digital Music at Queen Mary University of London.
http://www.eecs.qmul.ac.uk/~andrewm
(c) 2016 Augmented Instruments Laboratory: Andrew McPherson,
Astrid Bin, Liam Donovan, Christian Heinrichs, Robert Jack,
Giulio Moro, Laurel Pardue, Victor Zappi. All rights reserved.
The Bela software is distributed under the GNU Lesser General Public License
(LGPL 3.0), available here: https://www.gnu.org/licenses/lgpl-3.0.txt
*/
#include <Bela.h>
#include <cmath>
#include <libraries/Scope/Scope.h>
#include <libraries/WriteFile/WriteFile.h>
#include <libraries/Trill/Trill.h>
#include <vector>
std::vector<Trill*> touchSensor; // Trill object declaration
#define NUM_TOUCH 5 // Number of touches on Trill sensor
#define NUM_SENSORS 2 // Number of sensors.
// Location of touch on Trill Ring
float gTouchLocationCycle[NUM_SENSORS] = {0.0, 0.0};
// Size of touch on Trill Ring
float gTouchSizeCycle[NUM_SENSORS] = {0.0, 0.0};
// Location of touches on Trill Ring
float gTouchLocation[NUM_SENSORS][NUM_TOUCH] = {{ 0.0, 0.0, 0.0, 0.0, 0.0 },
{ 0.0, 0.0, 0.0, 0.0, 0.0 }};
// Size of touches on Trill Ring
float gTouchSize[NUM_SENSORS][NUM_TOUCH] = {{ 0.0, 0.0, 0.0, 0.0, 0.0 },
{ 0.0, 0.0, 0.0, 0.0, 0.0 }};
// Number of active touches
unsigned int gNumActiveTouches[NUM_SENSORS] = {0, 0};
//unsigned int gTaskSleepTime = 12000; // microseconds
float read_out[NUM_SENSORS] = {0.0, 0.0};
float past_read[NUM_SENSORS] = {0.0, 0.0};
float gPhase[NUM_SENSORS] = {0.0, 0.0}; // carrier phase
float gInverseSampleRate; // sampling period.
float gAmplitude=.6; // amplitude.
float peakFrequency[NUM_SENSORS] = {1000.0, 1000.0}; // peak frequency (Hz) at 0/360
float freqRange_aroundPeak = 500;//100.0; // range around center frequency (180 degrees = peakFrequency + freqRange_aroundPeak, Hz)
float past_read_out[NUM_SENSORS] = {0.0, 0.0};
float active_touch[NUM_SENSORS] = {0.0, 0.0};
unsigned int sample_num = 0;
// Browser-based oscilloscope
Scope gScope;
//output file.
WriteFile freq_mod_tone_bela;
void loop(void*)
{
float pastRead[NUM_SENSORS] = {0.0, 0.0};
float newRead[NUM_SENSORS] = {0.0, 0.0};
while(!Bela_stopRequested())
{
// Read locations from Trill sensor
for(unsigned int n = 0; n < NUM_SENSORS; ++n){
// read current sensor & number of active touches.
touchSensor[n]->readI2C();
gNumActiveTouches[n] = touchSensor[n]->getNumTouches();
// loop thru active touches, get size & location.
for(unsigned int i = 0; i < gNumActiveTouches[n]; i++) {
gTouchLocation[n][i] = touchSensor[n]->touchLocation(i);
gTouchSize[n][i] = touchSensor[n]->touchSize(i);
}
// For all inactive touches, set location and size to 0
for(unsigned int i = gNumActiveTouches[n]; i < NUM_TOUCH; i++) {
gTouchLocation[n][i] = 0.0;
gTouchSize[n][i] = 0.0;
}
if(touchSensor[n]->getNumTouches())
{
newRead[n] = gTouchLocation[n][0];//touchSensor[n]->compoundTouchLocation(); // get touch location for first touch (of 5).
gTouchLocationCycle[n] = newRead[n];
gTouchSizeCycle[n] = touchSensor[n]->compoundTouchSize();
pastRead[n] = newRead[n];
read_out[n] = newRead[n];
past_read[n] = pastRead[n];
active_touch[n]=1.0;
} else {
active_touch[n]=0.0;
}
}
//usleep(gTaskSleepTime);
}
}
bool setup(BelaContext *context, void *userData)
{
gInverseSampleRate = 1.0 / context->audioSampleRate;
// set up Trill sensors.
unsigned int num_sensors=0;
unsigned int i2cBus = 1;
for(uint8_t addr = 0x20; addr <= 0x50; ++addr)
{
Trill::Device device = Trill::probe(i2cBus, addr);
if(Trill::NONE != device && Trill::CRAFT != device)
{
num_sensors++;
touchSensor.push_back(new Trill(i2cBus, device, addr));
printf("%#4x (%3d) | %s\n", addr, addr, Trill::getNameFromDevice(device).c_str());
touchSensor.back()->setScanSettings(0,9); // check this.
touchSensor.back()->setAutoScanInterval(1);
touchSensor.back()->printDetails();
}
}
//touchSensor.printDetails();
// Set and schedule auxiliary task for reading sensor data from the I2C bus
Bela_runAuxiliaryTask(loop);
// Set up the oscilloscope
gScope.setup(NUM_SENSORS, context->audioSampleRate);
// generate sine tone representing modulation frequency range.
// output file.
freq_mod_tone_bela.setup("freq_mod_tone_bela.txt");
freq_mod_tone_bela.setFormat("%f %f %f %f %f\n");
freq_mod_tone_bela.setFileType(kText);
return true;
}
void render(BelaContext *context, void *userData)
{
for(unsigned int n = 0; n < context->audioFrames; n++) {
sample_num++;
float out_freqs[NUM_SENSORS] = {0.0, 0.0};
float frequency[NUM_SENSORS] = {0.0, 0.0};
float read_out_degrees[NUM_SENSORS] = {0.0, 0.0};
float y_coordinate_now[NUM_SENSORS] = {0.0, 0.0};
for(unsigned int t = 0; t < NUM_SENSORS; ++t) {
// if there is a touch on the sensor, then produce frequency based on finger position.
if ( active_touch[t] > 0 ){
//update touch counter.
//active_touch_cnt++;
// now convert position to value between 0 & 360.
read_out_degrees[t] = read_out[t] * 360.0;
// now compute sine of that value to get y-coordinate.
// if using bar sensor, don't use y-value.
if(t==1){
y_coordinate_now[t] = sinf(read_out_degrees[t]*M_PI/180.0) * 0.5; //NECESSARY WITH RING.
} else {
y_coordinate_now[t] = cosf(read_out_degrees[t]*M_PI/180.0) * 0.5;// if bar sensor, motion is along x-axis.
}
// determine frequency.
frequency[t] = map(y_coordinate_now[t], -0.5, 0.5, peakFrequency[t]-freqRange_aroundPeak, peakFrequency[t]+freqRange_aroundPeak);
float timestamp_ms = float(sample_num) * (1000/(context->audioSampleRate));
const float data_out_now[5]={ timestamp_ms, float(t), read_out[t], y_coordinate_now[t], frequency[t] };
// now write data out.
freq_mod_tone_bela.log(data_out_now,5);
gPhase[t] += 2.0f * (float)M_PI * frequency[t] * gInverseSampleRate;
if( gPhase[t] > M_PI ){
gPhase[t] -= 2.0f * (float)M_PI;
}
out_freqs[t] = 0.8f * sinf(gPhase[t]);
}
} // end looping through sensors.
for(unsigned int channel = 0; channel < context->audioOutChannels; channel++) {
float out = 0;
float stream_now=0;
for(int i=0;i<NUM_SENSORS;i++){
if(i==0){
stream_now=out_freqs[i];
} else if (i==1){
stream_now=out_freqs[i];
}
out += stream_now;
}
// Now write out audio.
audioWrite(context, n, channel, out);
}
gScope.log(out_freqs[0], out_freqs[1]);
// if stop requested, then write out file.
//if(Bela_stopRequested()){
// for(unsigned int line_out = 0; line_out < active_touch_cnt; line_out++){
// const float dat_out_current[4] = {data_out_now[line_out].x1, data_out_now[line_out].x2, data_out_now[line_out].x3, data_out_now[line_out].x4};
// freq_mod_tone_bela.log(dat_out_current,4);
// }
//}
}
}
void cleanup(BelaContext *context, void *userData)
{
}