Cabbage Logo
Back to Cabbage Site

How can I use the Serial Opcode to trigger a sound from an Arduino?

Why not:

void loop() {
  currentState = digitalRead(4);
  Serial.write(currentState);
}

Does this really lead to a 10 second latency in audio? I wish I had my Arduino to hand :frowning_face:

You could also looks a sending MIDI data from Arduino to Csound. I have read of several libraries to do this but never used any of them. This one looks interesting

Disclaimer: It’s the first one I found on github!!

Yeah the latency is pretty bad, oh I hadn’t considered using midi. That sounds promising actually. I will give that route a go. Thanks a million :blush:

Jut an update here, I discovered that changing the ksmps to lower values can get rid of the latency, however, it is still like it’s stopping for a millisecond on the serialRead and causing horrible distortion.

Also, I discovered that I cannot use my Arduino as a Midi device :sob: The one I have is a Duemilanove, which isn’t supported because of the Ftdi-chip.

That’s the one I loaned you? Yeah they are pretty old now.

Can you send me on a full simplfied .csd file. Maybe there is something else here I’m missing. The thing is that even if there is latency between the Arduino and Csound, it shouldn’t causes problems with the audio. If you recall, I ran a simple demo on my Windows laptop using an LDR. There was pretty bad latency between movement and sound, but the audio ran without any dropouts.

Yes, its that one, I’ve Invested in an Uno but it won’t be here on time.
Ok so I have changed the code so many times but this is the most recent one

Full csd file
serialRead.csd (597 Bytes)

Full ino File (as .txt so I could upload)
serial_write_button_value.txt (468 Bytes)

Also here is a quick video demo of the issue I am having with this code

I really feel like I need some kind of if statement to say
if(serialRead contains data) then
//do stuff

I think I am going to go down the c++ route for now as I already lost to much time on this.

I think you should definitely play around with the ksmps more. What happens when you set it to 32? Or 128?

You mean work on some plugins?

Finally, if you think that somehow Csound is waiting for the serial port to return a value, then parhaps you should be doing this:

void loop() {
  currentState = digitalRead(4);
  Serial.write(currentState);
}

When I last tested using an LDR I was doing just this, sending information on every frame.

So when testing with that code, it seems that setting the ksmps at 64, stops the crackling but still has very bad latency, that actually gets worse the longer it is running. Then if I set the ksmps to 32, there is virtually no latency but very distorted sound.

The C++ route, I managed to get serial communication working with this serial port class
https://playground.arduino.cc/Interfacing/CPPWindows/

Just started integrating it with Juce framework and have managed to get it playing audio on a button press from the Arduino.

Impressive! :wink:

searialRead reads one byte, and will wait for input. This is less than useful so I am actively looking at a replacement opcode. Thanks for our observations; most useful

best wishes

1 Like

Glad you are looking at this John. I have sent you some files that I have done based on this work of Rory and I_am_Lorde. It is a start. Tomorrow - many more.

Great, thanks for taking a look John! I think @I_Am_Lorde has ended up writing his own audio processing application in C++, but not everyone will have those particular skills! :rofl:

1 Like

Thanks guys, really appreciate it. I will keep an eye out for updates :slight_smile:

Apologies for missing this, but @rorywalsh that arduino midi library you linked to works just fine. In the last week I’ve midified an expression pedal with it.

The way to communicate with csound right now is midi, with many arduinos the easiest way is via serial midi and a program on the computer that pipes data from the serial port into the os’ midi layer, on linux it’s ttymidi, though I had to make custom settings and run it at 115200 baud. There are similar programs for windows and mac.

The arduino that can easily do usb midi is the leonardo, AFAIK using a default midi port in the midi library you linked to will make a leonardo show up as a midi device. A uno is still by default limited to showing up as a serial port on the host computer, but that can be changed on good quality unos that have all the real chips- the usb controller can be reflashed to make a uno show up as a midi human interface device. However with the normal solution, hiduino, once flashed with midi firmware you’ll need a programmer to reflash your uno. Another usb firmware option is mocolufa that allows you to dual boot midi and normal serial firmware by shorting two pins.

Mostly this info has come from the OpenTheremin midi implementation page.

hope this helps anyone who needs it.

Thanks for this, I was curious to know how well it might work. I think MIDI is a great way to go because it mean you’re instrument can have a live outside the Arduino. On the other hand, just yesterday it was announced on the Csound list that there are now new Arduino specific opcodes. I’ve attached the short paper on them. They are in the dev branch right now but will be in the next release.

arduino.pdf (78.5 KB)

thanks for that… It’ll be tomorrow that I get to it. Here’s my expression pedal code to speed things up for people as it’s nice and simple and I’ve used public domain code and might as well make it public domain too.

This is arduino code to midify an m-audio blackbox pedalboard. It’s an expression pedal paired with two momentary switches. You can find it in clearance centres currently as the blackbox it’s a pedalboard for has been discontinued.

I have a crapy uno currently and can’t flash the usb firmware, these are the custom settings for ttymidi

#include <MIDI.h>


struct MySettings : public midi::DefaultSettings
{
    static const bool UseRunningStatus = true; 
    static const long BaudRate = 115200;
};

// Create a 'MIDI' object using MySettings bound to Serial.
MIDI_CREATE_CUSTOM_INSTANCE(HardwareSerial, Serial, MIDI, MySettings);

//MIDI_CREATE_DEFAULT_INSTANCE()


/*
**using integer templates as a neat way to make these constants reside
**in the 32k of flash rather than 2k of ram
*/
template
<
  int16_t tPin, 
  int16_t tPressTime, 
  int16_t tDoublePressTime,
  uint8_t tMidiCC, 
  uint8_t tMidiChannel,
  int16_t tDebounceTime
  
>
class MidiButton 
{
protected:
    uint32_t mPressedTime;
    uint32_t mReleasedTime;
    bool mIsPressing;
    bool mIsLongDetected;
    uint8_t mPrevSteadyState;
    uint8_t mLastSteadyState;
    uint8_t mLastFlickerableState;
    uint8_t mCurrentState;
    uint32_t mLastDebounceTime;

public:

  MidiButton() : 
   mPressedTime(0),
   mReleasedTime(0), 
   mIsPressing(false),
   mIsLongDetected(false)   
  {
    pinMode(tPin, INPUT_PULLUP);

    mPrevSteadyState = digitalRead(tPin);
    mLastSteadyState = digitalRead(tPin);
    mLastFlickerableState = digitalRead(tPin);
    mLastDebounceTime = 0;
  }
  
  bool isPressed()
  {
    if(mPrevSteadyState == HIGH && mLastSteadyState == LOW)
      return true;
    else
      return false;
  }
  
  bool isReleased() 
  {
    if(mPrevSteadyState == LOW && mLastSteadyState == HIGH)
        return true;
    else
        return false;
  }

  void loop() 
  {
    uint32_t pressDuration = 0;
    uint32_t lastPressTime;
    mCurrentState = digitalRead(tPin);

    if (mCurrentState != mLastFlickerableState) 
    {
      mLastDebounceTime = millis();
      mLastFlickerableState = mCurrentState;
    }

    if((millis() - mLastDebounceTime) >= tDebounceTime) 
    {
      mPrevSteadyState = mLastSteadyState;
      mLastSteadyState = mCurrentState;
    }
    
    if(isPressed()) 
    {  
      lastPressTime = mPressedTime;
      mPressedTime = millis();
      mIsPressing = true;
      mIsLongDetected = false;

      if((mPressedTime - lastPressTime) <= tDoublePressTime) //it's a double click on
      {
        MIDI.sendControlChange(tMidiCC, 127, tMidiChannel); 
      }
      else //it's a single click on  
      {
        MIDI.sendControlChange(tMidiCC, 120, tMidiChannel); 
      }      
    }
    else if(isReleased())
    {  
      mReleasedTime = millis();
      mIsPressing = false;
      pressDuration = mReleasedTime - mPressedTime;      
      if(pressDuration < tPressTime)
      {
          MIDI.sendControlChange(tMidiCC, 50, tMidiChannel);//50 will be detected as an off, and this is release, but it's a short press 
          return; 
      }
      else
      {
        MIDI.sendControlChange(tMidiCC, 0, tMidiChannel);
      }
    }
    
    if(mIsPressing && !mIsLongDetected)
    {      
      pressDuration = millis() - mPressedTime;
      if(pressDuration > tPressTime)
      {
        mIsLongDetected = true;
        MIDI.sendControlChange(tMidiCC, 80, tMidiChannel);//80 will be detected as an on, and this is held down
      }
    }
  }
};

void send14bCC(uint8_t number, int16_t value, uint8_t channel) 
{
uint8_t msb=0, lsb=0;

  msb = (value >> 7) & 0x7F;
  lsb = value & 0x7F;
  MIDI.sendControlChange(number, msb, channel);
  MIDI.sendControlChange(number+32, lsb, channel);
}

template 
<
    int8_t tPin,
    int16_t tFilterSize,
    uint8_t tMidiCC,
    uint8_t tMidiChannel,
    int16_t tThreshold,
    int16_t tMaxTimeout,
    int16_t tMinTimeout
>
class MidiPot 
{
protected:
    
  int16_t mIdx;
  int32_t mTotal;
  int16_t mAverage;
  int16_t mLastStable;
  int16_t mFilter[tFilterSize];
  uint32_t mValueTimer;
  
public:
  
  MidiPot() : 

    mIdx(0),
    mTotal(0),
    mAverage(0),
    mLastStable(0),
    mValueTimer(0)
  {
    pinMode(tPin, INPUT); 
    for(int16_t i=0;i<tFilterSize;i++)
    {
      mFilter[i] = 0;
    }
  }

  int16_t value()
  {
    return mLastStable;
  }

  bool debounce()  
  {
    int16_t diff = mAverage - mLastStable;
    if(diff < 0) 
      diff *= -1;
        
      if((diff >= tThreshold) || ((diff > 0) && ((millis() - mValueTimer) < tMaxTimeout))
        && ((millis() - mValueTimer) > tMinTimeout))      
        {        
          mValueTimer = millis();
          mLastStable = mAverage;            
          return true;       
        }      
        return false;  
  } 

  void loop()
  {
    // moving average filter
    mTotal -= mFilter[mIdx];
    mFilter[mIdx] = analogRead(tPin) << 4;
    
    mValueTimer = millis();
    mTotal += mFilter[mIdx];
    mIdx += 1;
    if(mIdx >= tFilterSize) 
    {
      mIdx = 0;
    }
    mAverage = mTotal / tFilterSize;   
         
    if(debounce())
    {
      send14bCC(tMidiCC, mLastStable, 1);      
    }
  }  
};

#define PRESS 250
#define DOUBLE_PRESS 300


//Attach objects to pins and midi controller numbers
MidiPot<A0, 10, 4, 1, 25, 200, 10> expPedal;    //footPedal controller
MidiButton<9, PRESS, DOUBLE_PRESS, 64, 1, 5> leftBtn;  //sustain controller
MidiButton<12, PRESS, DOUBLE_PRESS, 67, 1, 5> rightBtn; //soft pedal controller


void setup()
{  
  MIDI.begin();
}


void loop()
{  
  expPedal.loop();   
  leftBtn.loop();   
  rightBtn.loop(); 
               
  delay(5);  
}

Wow, thanks for sharing, this is great :metal:

Oh I should add though I have the real pedal I haven’t hooked it up yet, I’ve tested this code with a 10k pot and two switches on a prototyping board with the wiper of the pot connected to A0, the left and right buttons (which are INPUT_PULLUP) are hooked up to 9 and 12.

I have more boards in the post, one of which is a leonardo micro, so I’ll be trying to bring this code up in the pedal board as a midi HID on it (edit: the leonardo) this week sometime.

Re those arduino opcodes: continuing down this path is going to be an awful lot like re-implementing a one off incompatible version of midi. Far better to just use midi I think. Though a very welcome csound improvement for dealing with arduinos would be to build the capabilities of ttyMidi into an opcode. Pass it a baud rate and device id (on linux default is /dev/ttyUSB0), pull the output out of the serial port and send it into csound’s midi subsystems. I’m sure there’s jitter and latency being introduced by one program pulling data out of the serial port and sending it through the kernel again via a virtual midi port to another application.

And of course midi already takes care of multiple event-types with channels and ports and everything.

If people really wanted to go the whole hog it’d be quite possible on linux to run osc to the host via a slip interface, as linux has a slip device driver. But with 10-bit analogue to digital converters and 2k ram I think osc is ludicrous overkill for an arduino.