Tuesday, February 12, 2013

Arduino Notes, Frequencies and Scales plus a Bonus Filter.

A quick post with a file I use a lot to experiment with Audio projects.

The file contains the frequencies used to generate the midi notes 0 to 127 in your Arduino projects. The file doesn't quite provide the frequency, instead it provides the phase increment required to generate the frequency. If you are familiar with Direct Digital Synthesis you will know that this is what we use in our Auduino projects to generate sine and other interesting waveforms at a given frequency.

The Illutron B Uses Direct Digital Synthesis, there is an introduction to the technique here -
http://rcarduino.blogspot.com/2012/08/the-must-build-arduino-project-illutron.html

Saving Memory
The note tables file uses PROGMEM to efficiently store the frequency table out of the way in program memory -

http://arduino.cc/en/Reference/PROGMEM

In addition to the 128 midi notes, two scales are provided, the pentatonic blues scale that will be familiar to anyone who has built and Auduino and the equally widely used C Minor blues scale. To preserve memory, these scales are also stored in progmem. You can add your own scales by following the same pattern.

Accessing The Notes

To access a note you can use -

uint16_t unPhaseIncrement = getMidiNotePhaseIncrement(sNote);

where sNote is between 0 and 127

To access a note from one of the scales based on an analogInput you can use -

uint16_t unPhaseIncrement = getCMBluesPhaseIncrement(map(aInput,0,1024,0,CMBLUES_NOTES));

In the sample code above, we are using the map function to map an analog input value held in aInput the range of notes available in the C Minor Blues scale.

A Simple Test Synthesiser
At the end of this post is a very simple synth which uses the note tables and is based on the popular Auduino with one bonus feature. I have included the low pass filter from the Mozzi Arduino audio libraries. The Mozzi filter is better than the simple hardware filters I have tried and is very easy to add to a project.

In the test sketch the filter can be driven in two ways based on the position of an analog input. At the bottom end of the input range, the filter is controlled directly by the input, at the top end of the range, the filter is controlled by an oscillator, the frequency of the oscillator is  adjustable within this range.

To keep things simple, the test sketch uses the same hardware as an Auduino so if you want to have a play around with the mozzi filter or creating your own scales - load up and play away.

Mozzi

The Mozzi filter can be downloaded from http://sensorium.github.com/Mozzi/

The Mozzi sound samples are very good quality, the project implements a range of synthesizer building blocks and is something to take a closer look at.

Arduino Note Tables -

// Arduino Note Tables rcarduino.blogspot.com

#ifndef _NOTETABLES_
#define _NOTETABLES_

#include "avr/pgmspace.h"

#define MIDI_NOTES 128
// used to convert midi note numbers into the increments required to generate the note in the ISR
PROGMEM unsigned int midiNoteToWavePhaseIncrement[MIDI_NOTES] =
{
 66 // 0,8.18,66.98,66                C
,70 // 1,8.66,70.96,70
,75 // 2,9.18,75.18,75
,79 // 3,9.72,79.65,79
,84 // 4,10.30,84.38,84
,89 // 5,10.91,89.40,89
,94 // 6,11.56,94.72,94
,100 // 7,12.25,100.35,100
,106 // 8,12.98,106.32,106
,112 // 9,13.75,112.64,112
,119 // 10,14.57,119.34,119
,126 // 11,15.43,126.43,126
,133 // 12,16.35,133.95,133            C
,141 // 13,17.32,141.92,141
,150 // 14,18.35,150.35,150
,159 // 15,19.45,159.29,159
,168 // 16,20.60,168.77,168
,178 // 17,21.83,178.80,178
,189 // 18,23.12,189.43,189
,200 // 19,24.50,200.70,200
,212 // 20,25.96,212.63,212
,225 // 21,27.50,225.28,225
,238 // 22,29.14,238.67,238
,252 // 23,30.87,252.86,252
,267 // 24,32.70,267.90,267           C - lowest note used on rcarduino ribbon synth
,283 // 25,34.65,283.83,283
,300 // 26,36.71,300.71,300
,318 // 27,38.89,318.59,318
,337 // 28,41.20,337.53,337
,357 // 29,43.65,357.60,357
,378 // 30,46.25,378.87,378
,401 // 31,49.00,401.40,401
,425 // 32,51.91,425.27,425
,450 // 33,55.00,450.55,450
,477 // 34,58.27,477.34,477
,505 // 35,61.74,505.73,505
,535 // 36,65.41,535.80,535           C
,567 // 37,69.30,567.66,567
,601 // 38,73.42,601.42,601
,637 // 39,77.78,637.18,637
,675 // 40,82.41,675.07,675
,715 // 41,87.31,715.21,715
,757 // 42,92.50,757.74,757
,802 // 43,98.00,802.79,802
,850 // 44,103.83,850.53,850
,901 // 45,110.00,901.11,901
,954 // 46,116.54,954.69,954
,1011 // 47,123.47,1011.46,1011       C
,1071 // 48,130.81,1071.60,1071
,1135 // 49,138.59,1135.32,1135
,1202 // 50,146.83,1202.83,1202
,1274 // 51,155.56,1274.36,1274
,1350 // 52,164.81,1350.13,1350
,1430 // 53,174.61,1430.42,1430
,1515 // 54,185.00,1515.47,1515
,1605 // 55,196.00,1605.59,1605
,1701 // 56,207.65,1701.06,1701
,1802 // 57,220.00,1802.21,1802
,1909 // 58,233.08,1909.38,1909
,2022 // 59,246.94,2022.92,2022
,2143 // 60,261.63,2143.20,2143       C
,2270 // 61,277.18,2270.64,2270
,2405 // 62,293.66,2405.66,2405
,2548 // 63,311.13,2548.71,2548
,2700 // 64,329.63,2700.27,2700
,2860 // 65,349.23,2860.83,2860
,3030 // 66,369.99,3030.95,3030
,3211 // 67,392.00,3211.18,3211
,3402 // 68,415.30,3402.12,3402
,3604 // 69,440.00,3604.42,3604
,3818 // 70,466.16,3818.75,3818
,4045 // 71,493.88,4045.83,4045
,4286 // 72,523.25,4286.41,4286      C
,4541 // 73,554.37,4541.29,4541
,4811 // 74,587.33,4811.33,4811
,5097 // 75,622.25,5097.42,5097
,5400 // 76,659.26,5400.53,5400
,5721 // 77,698.46,5721.67,5721
,6061 // 78,739.99,6061.89,6061
,6422 // 79,783.99,6422.36,6422
,6804 // 80,830.61,6804.25,6804
,7208 // 81,880.00,7208.85,7208
,7637 // 82,932.33,7637.51,7637
,8091 // 83,987.77,8091.66,8091
,8572 // 84,1046.50,8572.82,8572     C
,9082 // 85,1108.73,9082.58,9082
,9622 // 86,1174.66,9622.66,9622
,10194 // 87,1244.51,10194.85,10194
,10801 // 88,1318.51,10801.07,10801
,11443 // 89,1396.91,11443.33,11443
,12123 // 90,1479.98,12123.79,12123
,12844 // 91,1567.98,12844.71,12844
,13608 // 92,1661.22,13608.50,13608
,14417 // 93,1760.00,14417.70,14417
,15275 // 94,1864.65,15275.02,15275
,16183 // 95,1975.53,16183.31,16183
,17145 // 96,2093.00,17145.63,17145     C
,18165 // 97,2217.46,18165.16,18165
,19245 // 98,2349.32,19245.31,19245
,20389 // 99,2489.01,20389.70,20389
,21602 // 100,2637.02,21602.14,21602
,22886 // 101,2793.83,22886.67,22886
,24247 // 102,2959.95,24247.58,24247
,25689 // 103,3135.96,25689.42,25689
,27216 // 104,3322.44,27216.99,27216
,28835 // 105,3520.00,28835.39,28835
,30550 // 106,3729.31,30550.04,30550
,32366 // 107,3951.06,32366.63,32366
,34291 // 108,4186.01,34291.26,34291    C
,36330 // 109,4434.92,36330.32,36330
,38490 // 110,4698.64,38490.65,38490
,40779 // 111,4978.03,40779.41,40779
,43204 // 112,5274.04,43204.25,43204
,45773 // 113,5587.65,45773.32,45773
,48495 // 114,5919.91,48495.14,48495
,51378 // 115,6271.92,51378.79,51378
,54433 // 116,6644.87,54433.96,54433
,57670 // 117,7040.00,57670.76,57670
,61100 // 118,7458.62,61100.07,61100
,64733 // 119,7902.13,64733.26,64733
,3046 // 120,8372.02,68582.53,3046      C
,7124 // 121,8869.84,72660.64,7124
,11445 // 122,9397.27,76981.30,11445
,16022 // 123,9956.06,81558.77,16022
,20872 // 124,10548.07,86408.50,20872
,26010 // 125,11175.30,91546.65,26010
,31454 // 126,11839.81,96990.28,31454
,31454 // 127,11839.81,96990.28,31454 // this is wrong, need to calculate correct value, even though at 8Khz its wrapping around on every tick
};

// Pentatonic scale
// C D E G A C
// to map to midi note

#define PENTATONIC_NOTES 54

// provides an index of pentatonic notes in the midi note table

PROGMEM unsigned char sPentatonicNotes[PENTATONIC_NOTES] =
{
  0,   2,  4,  7,  9,
  12, 14, 16, 19, 21,
  24, 26, 28, 31, 33,
  36, 38, 40, 43, 45,
  48, 50, 52, 55, 57,
  60, 62, 64, 67, 69,
  72, 74, 76, 79, 81,
  84, 86, 88, 91, 93,
  96, 98,100,103,105,
 108,110,112,115,117,
 120,122,124,127
};

// C Minor Blues scale
// C D# F F# G A# C
// to map to midi note

#define CMBLUES_NOTES 65

// provides an index of pentatonic notes in the midi note table

PROGMEM unsigned char sCMBluesNotes[CMBLUES_NOTES] =
{
  0,   3,  5,  6,  7, 10,  // 6
  12, 15, 17, 18, 19, 22,  // 12
  24, 27, 29, 30, 31, 34,  // 18
  36, 39, 41, 42, 43, 46,  // 24
  48, 51, 53, 54, 55, 58,  // 30
  60, 63, 65, 66, 67, 70,  // 36  
  72, 75, 77, 78, 79, 82,  // 42
  84, 87, 89, 90, 91, 94,  // 48
  96, 99,101,102,103,106,  // 54
 108,111,113,114,115,118,  // 60
 120,123,125,126,127       // 65
};


unsigned int getMidiNotePhaseIncrement(unsigned char sNote)
{
 if(sNote >= MIDI_NOTES)
 {
   sNote = (MIDI_NOTES - 1);
 }

 return pgm_read_word(midiNoteToWavePhaseIncrement + (sNote));
}
unsigned int getPentatonicPhaseIncrement(unsigned char sPentatonicNote)
{
 if(sPentatonicNote >= PENTATONIC_NOTES)
  sPentatonicNote = (PENTATONIC_NOTES - 1);

 uint8_t sMidiIndex = pgm_read_byte(sPentatonicNotes + sPentatonicNote);

 return pgm_read_word(midiNoteToWavePhaseIncrement + sMidiIndex);
}

unsigned int getCMBluesPhaseIncrement(unsigned char sCMBluesNote)
{
 if(sCMBluesNote >= CMBLUES_NOTES)
  sCMBluesNote = (CMBLUES_NOTES - 1);
 
 uint8_t sMidiIndex = pgm_read_byte(sCMBluesNotes + sCMBluesNote);
 return pgm_read_word(midiNoteToWavePhaseIncrement + sMidiIndex);
}

#endif

The very simple, very lo-fi synth

// Very Lo Fi synth with Mozzi filter rcarduino.blogspot.com

#include "NoteTables.h"
#include "lowpass.h"

#define PWM_OUT_REG OCR2B

#define KEY_COUNT 20
#define KEY_WIDTH (1024/KEY_COUNT)

class CAudio
{
  public:
   CAudio(){}
   static void begin(uint8_t bStopTimer0Interrupts)
   {
     // Setup timer 1
     TCCR1A=0x0;          // set the timer prescaler to 8 = 16/8 = 2MHz
     TCCR1B=0x02;          // set the timer prescaler to 8 = 16/8 = 2MHz
     TIMSK1 |= (1<<OCIE1A);   // Enable output compare match interrupt on OCR1A
 
     TCCR2A=0B10110011;                                    //-8 bit audio PWM
     //TCCR0A=0x83;          // Set timer waveform generation mode to FAST PWM, clear OC0A On match, set at bottom - OC0A = digital pin 6.
     TCCR2B=0x01;          // Set to clock frequency, no prescaler

     pinMode(3,OUTPUT);
    
     if(bStopTimer0Interrupts)
     {
      // stops timer0 which triggers interrupts for the millis and micros functions
      // if we stop the timer, we get better audio, but loose millis and micros.
      TIMSK0 &= (~((1 << OCIE0A)| (1 << OCIE0B)));
     }
   }
};


#define SAW 0
#define SQUARE 1
#define RAMP 2


class COscilator
{
public:
  void setWaveform(uint8_t sWaveform)
  {
    m_sWaveform = sWaveform;
  }
  void setPhaseIncrement(uint16_t unPhaseIncrement)
  {
    m_unPhaseIncrement = unPhaseIncrement;
  }
  uint8_t hasTriggered()
  {
    return m_unPhaseAccumulator < m_unPhaseIncrement;
  }
  void trigger()
  {
    m_unPhaseAccumulator = 0;
  }
  void updatePhase()
  {
    m_unPhaseAccumulator += m_unPhaseIncrement;
  }
  uint8_t getSample()
  {
    uint8_t sSample = m_unPhaseAccumulator>>8;
    switch(m_sWaveform)
    {
      case SQUARE:
       (sSample >= 127) ? sSample = 255 : sSample = 0;
      break;
      case RAMP:
       sSample = ~sSample;
      break;
    }
    return sSample;
  }
protected:
  uint16_t m_unPhaseIncrement;
  uint16_t m_unPhaseAccumulator;
  uint8_t m_sWaveform;
};

COscilator Osc1,Osc2,LFO,LFOFilter;

uint16_t getPhaseIncrement(uint16_t aInput,uint8_t sOffset)
{
  uint8_t sKey = aInput/KEY_WIDTH;

  // find where on the ribbon the key starts 
  uint16_t unKeyStart = sKey * KEY_WIDTH;
 
  // find where on the ribbon the key ends
  uint16_t unKeyEnd = (sKey+1) * KEY_WIDTH;
 
  // map where the current input sits between the key start and key end to a frequency that sits at the same point between
  // the frequency of the key and the frequency of the next key
 
  return map(aInput,unKeyStart,unKeyEnd,getMidiNotePhaseIncrement(sKey+sOffset),getMidiNotePhaseIncrement(sKey+1+sOffset));
}

LowPassFilter lpFilter;
uint16_t gFilter;

void setup()
{
  Serial.begin(9600);
  CAudio::begin(true); 

  Osc1.setWaveform(RAMP);
  Osc2.setWaveform(RAMP);
  LFO.setWaveform(SAW);
  LFOFilter.setWaveform(RAMP);
}

void loop()
{
  uint16_t aInput = analogRead(1);
  if(aInput >= 1021)
  {
    Osc1.setPhaseIncrement(0);
    Osc2.setPhaseIncrement(0);
    LFO.setPhaseIncrement(0);
    LFOFilter.trigger();
  }
  else
  {
    uint8_t sOffset = 20;//((analogRead(1) >> 3)/12)*12;
    uint16_t unPhaseIncrement = getCMBluesPhaseIncrement(map(aInput,0,1024,0,CMBLUES_NOTES));
    Osc1.setPhaseIncrement(analogRead(2)<<2);
    Osc2.setPhaseIncrement(analogRead(3)<<5);

    //lpFilter.setCutoffFreq(analogRead(4)>>2);
    lpFilter.setResonance(analogRead(5)>>2);
   
    LFO.setPhaseIncrement(unPhaseIncrement);   
   
    uint16_t unFilter = analogRead(4);
    if(unFilter < 512)
    {
     LFOFilter.setPhaseIncrement(0);
     unFilter>>=1;
    
     uint8_t sreg = SREG;
     cli();
     gFilter = unFilter;
     SREG = sreg;
    }
    else
    {
     uint8_t sreg = SREG;
     cli();
     gFilter = unFilter;
     SREG = sreg;
    
     LFOFilter.setPhaseIncrement((gFilter-512));
    }
  }
}



#define DELAY_LENGTH 1024

// iterate the grains and LFO
SIGNAL (TIMER1_COMPA_vect)
{
  static uint8_t sDelayBuffer[DELAY_LENGTH];
  static uint16_t sDelayIndex;

  sDelayIndex++;
  if(sDelayIndex >= DELAY_LENGTH)
  {
    sDelayIndex = 0;
  }
 
  OCR1A += 125;

  Osc1.updatePhase();
  Osc2.updatePhase();
  LFO.updatePhase();
  LFOFilter.updatePhase();
 
  if(LFO.hasTriggered())
  {
    Osc1.trigger();
    Osc2.trigger();
  }
   
  if(gFilter < 512)
  {
    lpFilter.setCutoffFreq(gFilter>>1);
  }
  else
  {
   lpFilter.setCutoffFreq(LFOFilter.getSample());
  }
 
  uint16_t nOutput = lpFilter.next((Osc1.getSample()>>1)+(Osc2.getSample()>>1));
  if(nOutput > 255)
   nOutput = 255;
  PWM_OUT_REG = nOutput;

  // not being mixed in to output but easy to add, see rcarduino.blogspot.com
  sDelayBuffer[sDelayIndex] = PWM_OUT_REG;
}




And don't forget the Mozzi filter 'lowpass.h' from https://github.com/sensorium/Mozzi

Duane B

28 comments:

  1. Hi,

    this blog is truly an inspiration!

    Im wondering if its possible for the following code http://rcarduino.blogspot.co.uk/2012/01/how-to-read-rc-receiver-with.html to do a sanity check, ie only update nThrottleIn on 3 consecutive values that are identical or +-2% or something?

    My receiver spits out the odd glitch I think on channel 3 when I poke the throttle. I look at the serial output and amongst the good values, once or twice every three seconds a value that is really wrong is printed.

    Im using it to control a RC police car I built my kid. At this stage its a simple lights, siren deal...

    ReplyDelete
  2. Would be better if you asked this question following and RC Related post, but, here is a post that gives you a ready made solution - http://rcarduino.blogspot.ae/2012/03/reading-from-rc-reveiver-do-you-need.html

    Unless the car is behaving erratically I would not bother to address the occasional glitch as if you weren't measuring it with an Arduino you probably didn't notice it was there in the first place.

    If you using brushed motors, they can generate a lot of electical noise, people had been using these motors in RC Cars for 40 years without noticing the noise - its only when you plug and Arduino in and look at the numbers that you start to notice.

    Duane.

    ReplyDelete
  3. Thanks Duane for your response, I'm already using a 2.4G Futaba transmitter/receiver.
    Basically AUX channel was used to turn lights/siren on at either extreme, and off in the middle.
    I found that with them off, wiggling the steering and punching the throttle would glitch, and cause the siren and lights to come on for a millisecond maybe 3 times over 4 seconds...
    I managed to resolve the issue by using the RCArduinoFastLib instead of the initial code you had created.
    I was putting off trying that, I am by no means fully understanding much of this, but with your comments in the code and helpful nature I have a perfectly working toy for my son (and me)
    Thanks again, I've just compiled and tested it and it works a treat...

    ReplyDelete
  4. it's really nice and meanful. it's really cool blog. Linking is very useful thing.you have really helped lots of people who visit blog and provide them usefull information.
    best-bathroom-scales

    ReplyDelete
  5. We come to your door in or around Brisbane and remove that uninviting old car from your driveways or garages instantly.Old Car Removal Brisbane Junk Car Removal Brampton

    ReplyDelete
  6. This is a magnificent awakening post. Your theme and posting style is really awesome then others. keep up the good work and all the very best of luck!assignment writers online -
    essay writing -
    essay generator

    ReplyDelete
  7. Some of the primary factors driving the growth of the precision-guided munitions market are the increasing need for extremely accurate strike ammunition, rising asymmetric warfare, international disputes, and growing military modernization programs. Many countries are increasing their R&D spending and launching programs to develop next-generation precision weapons that are lightweight, compact, and reliable. The market for precision-guided munitions are being fueled by the use of satellite networks, navigation aids, and drones to kill high-value, long-range targets, as well as the demand for miniaturized weapons. Autonomous precision-guided warheads are equipped with electro-optical and high-resolution cameras to help locate and direct the vehicle to its intended destination. Growing demand for a multi-target precision strike, multi-mission weapons is expected to drive market growth over the forecast period.

    ReplyDelete
  8. The predictive maintenance market is expected to expand due to the integration of predictive maintenance with IIoT and the use of machine learning. The machine-learning algorithm can learn the normal behavior of the data and detect deviations in real-time. Sensors gather and interpret data from a range of sources, including CMMS and vital equipment sensors, in predictive maintenance. Equipment faults can be detected in advance when predictive maintenance and the IIoT are combined. As a result of the introduction of ‘Industry 4.0’ into the manufacturing world, companies are keen to introduce IIoT to obtain deeper insights into their operations. The COVID-19 pandemic has driven people all over the world to work from home. Industries suffered from a shortage of on-site staff during the pandemic, which decreased productivity.

    ReplyDelete
  9. Nice Information.

    Global 5G technology market will reach $388.82 billion by 2030.

    ReplyDelete
  10. Hi there,
    Such a good and timely post about building relationships! and I apperciate this.
    I have been putting on my blog and other social media as well.
    Glass Company in Dubai
    More-then 5 years of experience with an valuable reputation and reliability. We offer highest quality services as shower enclosure, shower scree installation, glass works in Dubai. Contact us for sandblasting planted glass with edge polishing and mirror Works in Dubai. IST Work LLC standing at the highest level of Dubai with professional glass supply and installation services.

    ReplyDelete
  11. This is a very nice one and gives in-depth information. I am really happy with the quality and presentation of the article. I’d really like to appreciate the efforts you get with writing this post. Thanks for sharing.
    Best Auto Cad Training Institute In Pune

    ReplyDelete
  12. I lost my job few months back and there was no way to get income for my family, things was so tough and I couldn't get anything for my children, not until a met a recommendation on a page writing how Mr Bernie Wilfred helped a lady in getting a huge amount of profit every 6 working days on trading with his management on the cryptocurrency Market, to be honest I never believe it but I took the risk to take a loan of $1000. and I contacted him unbelievable and I was so happy I earn $12,500 in 6 working days, the most joy is that I can now take care of my family I don't know how to appreciate your good work Mr. Bernie Doran God will continue to bless you for being a life saver I have no way to appreciate you than to tell people about your good services.
    For a perfect investment and good strategies contact Mr Bernie Doran via WhatsApp :+1(424)285-0682 or Telegram : @Bernie_doran_fx or Email : Bernie.doranfx01@gmail.com

    ReplyDelete
  13. The Wheat Market includes the production (volume), consumption (value and volume), trade in terms of import (value and volume) and export (value and volume), and price trend analyses of wheat across the globe. The market is segmented by geography into North America, Europe, Asia-Pacific, South America, and Africa. Asia-Pacific is observed as the largest and fastest-growing market for wheat. China and India mainly dominate in the Asia-Pacific region in terms of wheat production and consumption. In 2019, China was the leading country worldwide for wheat consumption (137 million metric ton). India ranked second, with the consumption of 102 million metric ton of wheat in the same year. Wheat is a major staple crop in the Asian countries, after rice.

    ReplyDelete
  14. Now the exact solution has been found to get rid of this disease. ClipAir Nasal Dilator is one such device that can be easily fitted in the nose to get rid of snoring all night. There is no need to do any kind of surgery to apply it in the nose. This clip can be easily fitted to the nose. This clip is very soft and comfortable, by applying it, there will be no pain in your nose. Its price is also not much. you can buy ClipAir Nasal Dilator from our website as we are authorised dealer available in Canada & USA. If you or your partner has this type of disease, you can buy it from here
    nasal dilator
    nasal dilation
    anti snoring solution

    ReplyDelete
  15. In this digitally growing world, the demand for Software Testing has increased a lot, there are many institutes and training centers for its training but why you should choose SevenMentor for Software Testing Classes in Pune is because it provides:1> Training from basics to advance level and its course is designed for all kinds of students.
    Software Testing Course In Pune

    ReplyDelete
  16. Moral Hacking is regularly alluded to as the method involved with infiltrating one's own PC/s or PCs to which one has official consent to do such as to decide whether weaknesses exist and to attempt preventive, restorative, and defensive countermeasures before a genuine trade-off to the framework happens.
    Ethical Hacking Course in Pune

    ReplyDelete
  17. I was looking for this kind of information and enjoyed reading this one. Keep posting. daycare silver spring md

    ReplyDelete
  18. Nice Post,
    Do you want to work as a software tester? Continue reading to learn how a software testing education may help you launch a career in software testing. Software testing is a crucial stage since it allows a developer to check for flaws and problems before releasing the application into a critical environment. Manual or automated technologies are used to put software components through their paces during software testing. Developers compare the components to the real requirements to look for gaps, gaps, or missing requirements. for more info Visit: Software Testing training in Pune

    ReplyDelete
  19. Our team is motivated and dedicated to providing you a seamless experience when it comes.Pest Control Lacey

    ReplyDelete
  20. A fighter must know how to use his knife while having a fight with his enemy or competitor because if it falls down then there would be a chance of the opponent to attack you. When you have a knife in your hand,
    mobile dispatch software

    ReplyDelete
  21. clinical research Studying human population health and illness is a topic that is expanding quickly in Pune. It is an essential part of the healthcare sector, and there is a big need for qualified people in this area.

    clinicalresearchcourses

    ReplyDelete
  22. Clinical research courses are an essential tool for students looking to pursue a career in the field of clinical research. These courses provide students with a comprehensive understanding of the field, including its purpose, the types of studies that are conducted, and the importance of regulatory compliance.
    clinicalresearchcourses

    ReplyDelete
  23. The goal of clinical research is to generate new knowledge that can be used to improve the quality of care for patients. This includes developing new treatments and diagnostic tools, as well as improving existing ones. Clinical research also plays an important role in policy-making and healthcare decision-making.

    https://wordpress.com/posts/fusiontechnologysolutionsblog.wordpress.com

    ReplyDelete
  24. Clinical research is important for many reasons. It helps to develop new treatments and improve existing ones, it can find new ways to prevent or diagnose conditions earlier, and it can also help to improve the quality of life for people with chronic conditions

    ReplyDelete
  25. I read your blog it's really great and thanks for sharing valuable knowledge. Hire Cheap Prado V8 for rent in Islamabad today.

    ReplyDelete
  26. Thank you for sharing this post and for your hard work. Can't wait to see more blogs like this!

    ReplyDelete
  27. Finally I found a blog that I was looking for. Hire best goods transport company in Lahore at lowest rates. In addition you can also find certified crane on rent in Lahore and mazda crane for rent services at your door-step. Thanks for sharing your blog.

    ReplyDelete

  28. "Your talent for storytelling is remarkable. More, please!"
    For more information visit us on "Clinical Research Courses"

    ReplyDelete