Serial sends an extra message that is just "0"

Hello all, this is my first post here, I hope I'm posting in the right place. I'm very new to Arduino and electronics and have been trying to work on something that is beyond my capabilities, but I'm getting tantalisingly close to a working prototype, but I have a bug that I am struggling to fix.

My project is to be able to send digital data from Pure Data (a visual programming music application) on my laptop to Arduino, then output as voltage via a quad DAC to a modular synth. This will basically allow me to send voltage to my synth to modulate and sequence it in all sorts of ways.

Phase one of my project is just to control the 0-5 volts going from Arduino to the synth via Pure Data. Next phases will be to increase the output voltage to -5/+5v, and to add more DACs, but that is for later.

So far I have successfully sent voltage from Arduino to the synth and modulated via Pure Data on the laptop, but the sound I am getting is a bit garbled/glitchy while being modulated and I believe it is because each message that is sent via serial is being preceded by a separate message "0" - so for example, if I assign volume to a potentiometer, each micro turn of the pot would set the volume to zero for a micro second before then setting the correct value. At least, that's what I think is happening.

Here's a screenshot from Pure Data showing what should be a single message received via serial:
image

I get the same result when I disconnect from Pure Data and send manually via the serial monitor in the Arduino IDE.

I'll paste my sketch below. I suspect my problem is something to do with the index variable that is set to 0 near the beginning of the loop section, but I haven't been able to find an alternative solution to that section of the code. Any unrelated improvements to the code are welcome, I am learning.

/* 

Assigns value ranges to the 4 outputs of the MCP4728 quad DAC, which can then be controlled via Pure Data.

Based on these video tutorials by Sound Simulator (https://www.youtube.com/@SoundSimulator) on Youtube:
https://www.youtube.com/watch?v=PaWp6zny83I&list=PLyFkFo29zHvC5oiRAdwvwxADUeLhCm3lV&index=15
https://www.youtube.com/watch?v=zdYGiBlbBP8&list=PLyFkFo29zHvC5oiRAdwvwxADUeLhCm3lV&index=15

*/

// include dependencies for MCP4728 quad DAC
#include <Adafruit_MCP4728.h>
// this library allows you to communicate with I2C devices
#include <Wire.h>

Adafruit_MCP4728 mcp;

// Initiate variables
char buffer[40];
int index = 0;
int value = 0;
int CV1 = 0;
int CV2 = 0;
int CV3 = 0;
int CV4 = 0;

// the setup function runs once when you press reset or power the board
void setup(void) {
  
  // start serial connection and set the 'Baud rate' (serial communication speed in bits per second). The baud rate must be set to the same value in Pure Data in order for the serial communication to work.
  Serial.begin(115200);
  
  // will pause Zero, Leonardo, etc until serial console opens
  while (!Serial) {
    delay(10);
  }

  // send message to Pure Data to confirm we are connected
  Serial.println("Adafruit MCP4728 test!");

  // try to initialize MCP4728
  if (!mcp.begin()) {
    Serial.println("Failed to find MCP4728 chip");
    while (1) {
      delay(10);
    }
  }

}

// the loop function runs over and over again forever
void loop() {

  // check to see if anything is available in the serial receive buffer
  while (Serial.available() > 0) {

    // receive data sent to [comport] left inlet in Pure Data
    index = 0;
    do {
      buffer[index] = Serial.read();
      if (buffer[index]!=-1) {
        index++;
      }
    }
    while (buffer[index-1]!=32);

    // convert value from ascii to integer
    value = atoi(buffer);
    
    // send converted value back to [comport] left outlet in Pure Data for debugging
    Serial.println(value);

    // assign value range to cv 1
    if (value >= 0 && value <= 4095) { //0 to 4095
      CV1 = value;
      mcp.setChannelValue(MCP4728_CHANNEL_A, value);
    }

    // assign value range to cv 2
    if (value >= 4096 && value <= 8191) { //4096 to 8191
      CV2 = value - 4096;
      mcp.setChannelValue(MCP4728_CHANNEL_B, CV2);
    }

    // assign value range to cv 3
    if (value >= 8192 && value <= 12287) { //8192 to 12287
      CV3 = value - 8192;
      mcp.setChannelValue(MCP4728_CHANNEL_C, CV3);
    }

    // assign value range to cv 4
    if (value >= 12288 && value <= 16383) { //12288 to 16383
      CV4 = value - 12288;
      mcp.setChannelValue(MCP4728_CHANNEL_D, CV4);
    }

    // delay(5);
  }    
  
}

Watch out. This loop has the potential for the 40-byte buffer to be written out of bounds.

Note that most terminal programs will append a CR (ASCII 13) and/or LF (ASCII 10) to the sent character array. I suspect this has something to do with your problem.

In tracing problems like these, start by printing the contents of buffer[] after receipt. You're now only printing the atoi-converted value, but have a look at what's really there. I suspect the CR+LF results in a second value of 0 being recorded for every Serial receipt.

I had a play around with looking at the buffer directly and went further up the chain to Pure Data found saw I was getting some unexpected ascii codes at the beginning of the messages being sent from PD to Arduino.

The [fudiparse] object I am using in PD to convert the number to ascii was adding the ascii for 'float' (which I guess wasn't being interpreted by atoi because it isn't an integer?) followed by a 13, which was resulting in the extra 0. The modulation of the synth now sounds smooth :ok_hand:

So I guess this was a Pure Data problem rather than an Arduino problem, but it was hard to tell where the problem lay with my lack of experience. Very pleasing to find the solution though. I suspect I will be back again with more problems to solve in the future. Thanks for the help.

First of all, what is the incoming data format? The code you posted here seems to use a space (decimal 32) as separator, and there's no other "special" character (like CR and/or LF, or '\r' and '\n') so you first need to clarify this to us before proceeding.
And the code where you collect the data seems to me to be not enough consistent.
I usually let the loop() flow until I receive the "terminator" character, instead of running inside a while() while waiting for the end of the message data.
For instance (if you're sure the incoming data does not contain '\r' or '\n', anyway you could think of filtering them out), if the data is just being sent in sequence with a single space as separator, you could change it like this:

void loop() {
  if (Serial.available() > 0) {
    char c = Serial.read();
    if (c != ' ') {
      if (c != '\r' && c != '\n') {
        buffer[index++] = c;
        // Always add the string terminator
        buffer[index++] = '\0';
      }
    }
    else
    { // Separator received!
      // Data is complete, process it:
      value = atoi(buffer);
      Serial.println(value);

... and so on ...
 
   }
}

Apologies @docdoc, I'm converting a number to ascii in Pure Data then sending it to Arduino. Currently I'm appending 32 and 13 to each message, so for example if I want to send "1910" it gets converted to "49 57 49 48 32 13", then sent to serial.

I have control over what gets appended, so I chose to add "32 13" based on code I stumbled upon while trying to make this work, or on a tutorial I watched somewhere. I don't know if it is good or bad or if it would have been better to not add one or the other.

I will try to process your suggestion for improving the loop when I have some mental bandwidth later, thanks.

Ok, now it's quite clearer to me. Anyway, in protocols you better use a single terminator character, making it easier to recognise and process.

What I can suggest you is getting rid of the unnecessary space and use just che byte 13 (aka '\n').

This way the code now becomes even simpler:

void loop() {
  if (Serial.available() > 0) {
    char c = Serial.read();
    if (c != '\n') {
      buffer[index++] = c;
      // Always add the string terminator to keep consistency
      buffer[index++] = '\0';
    }
    else
    { // Separator received!
      // Data is complete, process it:
      value = atoi(buffer);
      Serial.println(value);

... and so on ...
 
   }
}

PS: the anomalous "0" value is probably the '\n' character, so atoi() will return zero.