Hello all.

I want to get the position of my ACE-128 encoder. I’ve got it working with C++, but am working to do the same in Supercollider (this is because, from what I’ve read, it is impossible to run a C++ program decoding the encoder and then pipe this information to a Supercollider instance running in parallel — if there’s a clean way, then I’m all ears!).

The rotary encoder outputs a number between 0 and 255. These values serve as indices which are used to look up the encoder’s true position stored in an array. With the code below, I’m just trying to test getting the digital inputs, converting these to a decimal value, and then looking up the encoder’s position in the map array. The “~rotaryMonitor” is inspired by this: https://forum.bela.io/d/669-triggering-synths-with-digitalin-in-supercollider and I’m intending to somehow take a similar approach… if of course I can actually figure out how to map the encoder’s output to its positions.

It seems that the DigitalIn() function doesn’t immediately output an integer value, which in turn means a “DigitalIn” object is being used when I try to calculate the decimal value, and in turn a non-integer object is used for the array index. In the end, an error is thrown saying that the Index is not an Integer.

I understand that Supercollider on Bela is instantiating a hardcoded synth on a server, and then I need to communicate through OSC from the client to update parameters. The goal is to somehow get my 128-position encoder’s changes calculated and piped over to the server at control rate, but I am having trouble thinking the “Supercollider way”. Does anyone have guidance on this?


// variables for reading Encoder
~gEncoderPin1 = 2;
~gEncoderPin2 = 3;
~gEncoderPin3 = 4;
~gEncoderPin4 = 5;
~gEncoderPin5 = 6;
~gEncoderPin6 = 7;
~gEncoderPin7 = 8;
~gEncoderPin8 = 9;

~encoderMap_12345678 = Array[
  0xFF,0x38,0x28,0x37,0x18,0xFF,0x27,0x34,0x08,0x39,0xFF,0xFF,0x17,0xFF,0x24,0x0D,
  0x78,0xFF,0x29,0x36,0xFF,0xFF,0xFF,0x35,0x07,0xFF,0xFF,0xFF,0x14,0x13,0x7D,0x12,
  0x68,0x69,0xFF,0xFF,0x19,0x6A,0x26,0xFF,0xFF,0x3A,0xFF,0xFF,0xFF,0xFF,0x25,0x0E,
  0x77,0x76,0xFF,0xFF,0xFF,0x6B,0xFF,0xFF,0x04,0xFF,0x03,0xFF,0x6D,0x6C,0x02,0x01,
  0x58,0xFF,0x59,0xFF,0xFF,0xFF,0xFF,0x33,0x09,0x0A,0x5A,0xFF,0x16,0x0B,0xFF,0x0C,
  0xFF,0xFF,0x2A,0x2B,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x15,0xFF,0x7E,0x7F,
  0x67,0xFF,0x66,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x5B,0xFF,0xFF,0xFF,0xFF,0xFF,
  0x74,0x75,0xFF,0xFF,0x73,0xFF,0xFF,0xFF,0x5D,0x5E,0x5C,0xFF,0x72,0x5F,0x71,0x00,
  0x48,0x47,0xFF,0x44,0x49,0xFF,0xFF,0x1D,0xFF,0x46,0xFF,0x45,0xFF,0xFF,0x23,0x22,
  0x79,0xFF,0x7A,0xFF,0x4A,0xFF,0xFF,0x1E,0x06,0xFF,0x7B,0xFF,0xFF,0xFF,0x7C,0x11,
  0xFF,0xFF,0xFF,0x43,0x1A,0xFF,0x1B,0x1C,0xFF,0x3B,0xFF,0xFF,0xFF,0xFF,0xFF,0x0F,
  0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x05,0xFF,0xFF,0xFF,0x6E,0xFF,0x6F,0x10,
  0x57,0x54,0xFF,0x2D,0x56,0x55,0xFF,0x32,0xFF,0xFF,0xFF,0x2E,0xFF,0xFF,0xFF,0x21,
  0xFF,0x53,0xFF,0x2C,0x4B,0xFF,0xFF,0x1F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x20,
  0x64,0x3D,0x65,0x42,0xFF,0x3E,0xFF,0x31,0x63,0x3C,0xFF,0x2F,0xFF,0xFF,0xFF,0x30,
  0x4D,0x52,0x4E,0x41,0x4C,0x3F,0xFF,0x40,0x62,0x51,0x4F,0x50,0x61,0x60,0x70,0xFF];

// 8-Bit to decimal function for rotary encoder
~binaryToDecimal = {
	arg a, b, c, d, e, f, g, h;
	(a * 128) + (b * 64) + (c * 32) + (d * 16) + (e * 😎 + (f * 4) + (g * 2) + h;
};

// Just mapping the rotary encoder's 8 pins to the counting function
~rotaryToDecimal = {
	~binaryToDecimal.value(
		DigitalIn.kr(~gEncoderPin8),
		DigitalIn.kr(~gEncoderPin7),
		DigitalIn.kr(~gEncoderPin6),
		DigitalIn.kr(~gEncoderPin5),
		DigitalIn.kr(~gEncoderPin4),
		DigitalIn.kr(~gEncoderPin3),
		DigitalIn.kr(~gEncoderPin2),
		DigitalIn.kr(~gEncoderPin1)
	);
};

s = Server.default;

// Set up options for the Bela
s.options.numAnalogInChannels = 2;
s.options.numAnalogOutChannels = 2;
s.options.numDigitalChannels = 16;
s.options.maxLogins = 4;
s.options.bindAddress = "0.0.0.0"; // allow anyone on the network connect to this server

s.options.blockSize = 16;
s.options.numInputBusChannels = 2;
s.options.numOutputBusChannels = 2;

s.waitForBoot {
	~rotaryMonitor = {
		var d = ~rotaryToDecimal.value();
		var pos = ~encoderMap_12345678.at(d); // Throws exception... "Index not an Integer"
		pos.postln;
	}.play;
	
	// Simple synth
	{RLPF.ar(Dust.ar([1, 100]), LFNoise1.ar([1, 0.2]).range(100, 3000), 0.001)}.play;
};

ServerQuit.add({ 0.exit }); // quit if the button is pressed

    sigh

    The issue is not entirely resolved, but this article has been on my radar, and only just after writing out this forum post did it click: https://tai-studio.org/2016/08/12/bela-rotary.html

    Hopefully I’ll have it working soon and will post the result for anyone else who ever needs it.

    It’s working.

    The magic sauce seems to be using Select. I also switched to using [].sum for adding up the binary places, but didn’t test whether that was decisive.

    // variables for reading Encoder
    ~gEncoderPin1 = 2;
    ~gEncoderPin2 = 3;
    ~gEncoderPin3 = 4;
    ~gEncoderPin4 = 5;
    ~gEncoderPin5 = 6;
    ~gEncoderPin6 = 7;
    ~gEncoderPin7 = 8;
    ~gEncoderPin8 = 9;
    
    ~encoderMap_12345678 = Array[
      0xFF,0x38,0x28,0x37,0x18,0xFF,0x27,0x34,0x08,0x39,0xFF,0xFF,0x17,0xFF,0x24,0x0D,
      0x78,0xFF,0x29,0x36,0xFF,0xFF,0xFF,0x35,0x07,0xFF,0xFF,0xFF,0x14,0x13,0x7D,0x12,
      0x68,0x69,0xFF,0xFF,0x19,0x6A,0x26,0xFF,0xFF,0x3A,0xFF,0xFF,0xFF,0xFF,0x25,0x0E,
      0x77,0x76,0xFF,0xFF,0xFF,0x6B,0xFF,0xFF,0x04,0xFF,0x03,0xFF,0x6D,0x6C,0x02,0x01,
      0x58,0xFF,0x59,0xFF,0xFF,0xFF,0xFF,0x33,0x09,0x0A,0x5A,0xFF,0x16,0x0B,0xFF,0x0C,
      0xFF,0xFF,0x2A,0x2B,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x15,0xFF,0x7E,0x7F,
      0x67,0xFF,0x66,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x5B,0xFF,0xFF,0xFF,0xFF,0xFF,
      0x74,0x75,0xFF,0xFF,0x73,0xFF,0xFF,0xFF,0x5D,0x5E,0x5C,0xFF,0x72,0x5F,0x71,0x00,
      0x48,0x47,0xFF,0x44,0x49,0xFF,0xFF,0x1D,0xFF,0x46,0xFF,0x45,0xFF,0xFF,0x23,0x22,
      0x79,0xFF,0x7A,0xFF,0x4A,0xFF,0xFF,0x1E,0x06,0xFF,0x7B,0xFF,0xFF,0xFF,0x7C,0x11,
      0xFF,0xFF,0xFF,0x43,0x1A,0xFF,0x1B,0x1C,0xFF,0x3B,0xFF,0xFF,0xFF,0xFF,0xFF,0x0F,
      0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x05,0xFF,0xFF,0xFF,0x6E,0xFF,0x6F,0x10,
      0x57,0x54,0xFF,0x2D,0x56,0x55,0xFF,0x32,0xFF,0xFF,0xFF,0x2E,0xFF,0xFF,0xFF,0x21,
      0xFF,0x53,0xFF,0x2C,0x4B,0xFF,0xFF,0x1F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x20,
      0x64,0x3D,0x65,0x42,0xFF,0x3E,0xFF,0x31,0x63,0x3C,0xFF,0x2F,0xFF,0xFF,0xFF,0x30,
      0x4D,0x52,0x4E,0x41,0x4C,0x3F,0xFF,0x40,0x62,0x51,0x4F,0x50,0x61,0x60,0x70,0xFF];
    
    // Bit counting function for rotary encoder
    ~bitCounter = {
    	arg a, b, c, d, e, f, g, h;
    	[(a * 128), (b * 64), (c * 32), (d * 16), (e * 8), (f * 4), (g * 2), h].sum;
    };
    
    ~rotaryToDecimal = {
    	~bitCounter.value(
    		DigitalIn.kr(~gEncoderPin8),
    		DigitalIn.kr(~gEncoderPin7),
    		DigitalIn.kr(~gEncoderPin6),
    		DigitalIn.kr(~gEncoderPin5),
    		DigitalIn.kr(~gEncoderPin4),
    		DigitalIn.kr(~gEncoderPin3),
    		DigitalIn.kr(~gEncoderPin2),
    		DigitalIn.kr(~gEncoderPin1)
    	);
    };
    
    s = Server.default;
    
    // Set up options for the Bela
    s.options.numAnalogInChannels = 2;
    s.options.numAnalogOutChannels = 2;
    s.options.numDigitalChannels = 16;
    s.options.maxLogins = 4;
    s.options.bindAddress = "0.0.0.0"; // allow anyone on the network connect to this server
    
    s.options.blockSize = 16;
    s.options.numInputBusChannels = 2;
    s.options.numOutputBusChannels = 2;
    
    s.waitForBoot {
    	~rotaryMonitor = {
    		var decimal = ~rotaryToDecimal.value();
    		var position = Select.kr(decimal, DC.kr(~encoderMap_12345678));
    		position.poll;
    	}.play;
    	
    	// Simple synth
    	{RLPF.ar(Dust.ar([1, 100]), LFNoise1.ar([1, 0.2]).range(100, 3000), 0.001)}.play;
    };
    
    ServerQuit.add({ 0.exit }); // quit if the button is pressed

      LetItLearn r (this is because, from what I’ve read, it is impossible to run a C++ program decoding the encoder and then pipe this information to a Supercollider instance running in parallel — if there’s a clean way, then I’m all ears!).

      One way to do this could be by creating a SuperCollider ugen, which can be written in C++. But then, I guess native Supercollider options are easier to maintain/modify/expand.

      LetItLearn It’s working.

      Great! I understand next to nothing about Supercollider, so I am not sure how it works, but great news!

      Btw, why are you using an absolute encoder instead of something like an endless potentiometer (e.g. :Taiwan Alpha's RV112FF? They seem to be cheaper, higher resolution and you can have more at once on Bela: each needs two analog ins so you can have up to 4 with a standard Bela cape (or up to 32 with a multiplexer capelet); while you can only have 2 of your encoders (or 3 if you wire one up to the 8 analog inputs).

      Btw, I am also wondering if you can use 8 appropriately-chosen resistors per each AEC-128 encoder to use it as a "string DAC" and then read it with one single ADC. A quick glance at the pin output codes hints that you'd still need to read -> quantise -> convert and that adjacent positions may have very different codes (i.e.: non monotonic relationship), which could cause troubles ... but 128 positions is not too many to be able to distinguish between with our 16 bit ADCs ... Anyhow maybe this is just tangential and not interesteing for your needs / applications.

      • dws likes this.