nathanRamaNoodles / Noodle-Synth

A User-friendly Arduino/Teensy Library to play RTTL/MIDI with infinite polyphonic notes; it has full control over volume, pitch, and music. No shields needed(Just a speaker).
https://create.arduino.cc/projecthub/nathan_ramanathan/play-music-without-delay-40e51b
MIT License
138 stars 14 forks source link

Servo and Noodle-Synth #20

Closed dwjbosman closed 4 years ago

dwjbosman commented 4 years ago

If I try to use both Servo 1.1.7 and NoodleSynth 4.0.0 in one sketch on Arduino Uno it seems that only one of the libraries will work:

  buzzer.begin(CHB, TRIANGLE, ENVELOPE0, 0);   //CHB means pin 3 on Arduino UNO, CHA means pin 11
  servo.attach(8);
  servo.write(100);

  // no sound 

or

  servo.attach(8);
  servo.write(100);

  buzzer.begin(CHB, TRIANGLE, ENVELOPE0, 0);   //CHB means pin 3 on Arduino UNO, CHA means pin 11

  // no servo 

In the documentation you describe having solved the incompatibility. Should I do anything special?

nathanRamaNoodles commented 4 years ago

Try using CHA instead of CHB

dwjbosman commented 4 years ago

Using CHA I'm getting garbled/corrupted sound.

I only need one servo. Should I use specific pin combinations for the servo and the sound output?

dwjbosman commented 4 years ago

I looked at the source code. The servo library uses Timer1.

Your library allows to set USE_TIMER1. I did not acctivate this setting. I see that the library then uses timer 2. However

In SynthEngine.cpp:

#     if defined(USE_TIMER1)
SIGNAL(TIMER2_COMPB_vect)
#     else
SIGNAL(TIMER1_COMPB_vect)
#     endif

Shouldn't this be the other way around?

Also in synthEngine::begin you are setting timer 1 irrespective of USE_TIMER1. This will conflict with the Servo library. I have tried to do a quick update. Unfortunately it's not working.

void synthEngine::begin(unsigned char voice, unsigned char d)
{
  numVoice++;
  #if defined(__AVR_ATmega32U4__)||defined(ESP8266)
  begin(voice);
  #else
  #  if defined(__AVR__)
  cli();
  #    if defined(__AVR_ATmega2560__)
  TCCR4A = 0x00;                                  //-Start audio interrupt
  TCCR4B = 0x09;
  OCR4A=16000000.0 / FS_music;              //-Auto sample rate
  SET(TIMSK4, OCIE4B);                            //-Start audio interrupt
  #    else
  //mTCCR1A = 0x00;                                  //-Start audio interrupt
  mTCCR2A = 0x00;                                 <-------changed
  #   if defined(USE_TIMER1)
  mTCCR1B = (1 << CS21);
  #   else
  // mTCCR1B = 0x09;
  mTCCR2B = 0x09;  <-------changed
  #   endif
  //mOCR1A=16000000.0 / FS_music;               //-Auto sample rate
  mOCR2A=16000000.0 / FS_music;             <-------changed
  //SET(mTIMSK1, mOCIE1B);                            //-Start audio interrupt
  SET(mTIMSK2, mOCIE2B);                            <-------changed
  #    endif
nathanRamaNoodles commented 4 years ago

I believe even if the servo were to work, the audio quality on the Arduino will be affected. I think a Teensy board or an ESP32 is a much better upgrade. The atmega328 has only 3 timers, of which 2 can be used because timer 0 is millis(). Timer 1 is a 16-bit timer, and timer 2 is an 8-bit timer. By default, the library uses Timer 2. The problem is that the servo timer is interfering with the high-frequency PWM of timer 2. I'm not sure how to solve it at the moment. http://www.righto.com/2009/07/secrets-of-arduino-pwm.html

nathanRamaNoodles commented 4 years ago

I'm running this on a teensy 3.2 and it works without a sweat:

#include <NoodleSynth.h>
#include <Servo.h>
synthEngine mixer(20E3);
const char song[] PROGMEM = "::e,8f,8g,8a,8b,8c1,8d1,8e1";  //the C major scale. This format is known as RingTone Transfer Language or RTTL(It was used by Nokia's phone company).
const char song2[] PROGMEM = ":d=4:c,d,e,f";
const char song3[] PROGMEM = ":o=6,d=2:16a,16b,8a,4b";
const char song4[] PROGMEM = ":o=5,d=2:32g,32a,32b,32c";
MusicWithoutDelay instrument(song);          //d=4 means that every note without a number in front of the letter is assumed to be a quarter note.
MusicWithoutDelay instrument2(song2);
MusicWithoutDelay instrument3(song3);
MusicWithoutDelay instrument4(song4);
Servo myservo;  // create servo object to control a servo
int pos = 0;    // variable to store the servo position

void setup() {                               //For details on the RTTL format, look at https://github.com/nathanRamaNoodles/MusicWithoutDelay-LIbrary documentation
  // put your setup code here, to run once:
  myservo.attach(9);  // attaches the servo on pin 9 to the servo object
  instrument.begin(CHB, TRIANGLE, ENVELOPE0, 0);
  instrument2.begin(TRIANGLE, ENVELOPE0, 0);
  instrument3.begin(TRIANGLE, ENVELOPE0, 0);
  instrument4.begin(TRIANGLE, ENVELOPE0, 0);
  Serial.begin(115200);
  unsigned long t = instrument.getTotalTime();  //spits out total time in milliseconds
  Serial.println(F("Type '1','2','3', or '4' to mute the corresponding instrument."));

  Serial.print("Total Time: "); Serial.println(t / 1E3);
}
uint32_t pMillis = 0;
void loop() {
  // put your main code here, to run repeatedly:
  uint32_t cMillis = millis();
  if (cMillis - pMillis > 250) {
    myservo.write(pos % 180);
    pos+=5;
    pMillis = cMillis;
  }

  instrument.update();  //but its important to recognize that this library depends
  instrument2.update();
  instrument3.update();
  instrument4.update();
  if (instrument.isEnd()) {          //on your main code not having delay().  Also, it increases stability, so your welcome :D
    //    instrument.pause();     //pauses song
    //    instrument2.pause();
    //    instrument3.pause();
    //    instrument4.pause();
  }
  if (Serial.available()) {
    char str = Serial.read();
    switch (str) {
      case 's':  //type 's' in Serial monitor.
        //instrument.skipTo(instrument.getTotalTime() * (0.50)); //skip to halfway point
        Serial.println("What's it to ya?  I don't use delay, and you shouldn't either.\n Star this project on Github, and spread this good news to others.");
        break;
      case '1':  //type 's' in Serial monitor.
        instrument.mute(!instrument.isMuted());
        break;
      case '2':  //type 's' in Serial monitor.
        instrument2.mute(!instrument2.isMuted());
        break;
      case '3':  //type 's' in Serial monitor.
        instrument3.mute(!instrument3.isMuted());
        break;
      case '4':  //type 's' in Serial monitor.
        instrument4.mute(!instrument4.isMuted());
        break;
    }
  }
}
dwjbosman commented 4 years ago

Ok, I'll try it using a teensy 3.2

dwjbosman commented 4 years ago

On the teensy 3.2 it works fine.

One last question. On the Uno I noticed that the speaker (which I have connected to a PAM8403 amplifier) becomes luke warm, while on the Teensy it becomes really hot. Any idea why?

dwjbosman commented 4 years ago

I added a call to "analogWriteFrequency(A6, 375000);" to the setup function. The sound output is now much cleaner and the speaker doesn't run hot anymore.