element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • About Us
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
Personal Blogs
  • Community Hub
  • More
Personal Blogs
Legacy Personal Blogs Simple Arduino Music Box: Envelopes
  • Blog
  • Documents
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: jc2048
  • Date Created: 31 May 2018 11:55 AM Date Created
  • Views 5076 views
  • Likes 15 likes
  • Comments 10 comments
  • musicmakerch
  • ardintermediate
  • arduino_projects
  • envelope generator
  • uno
  • music
  • arduino uno project
  • arduino
  • music box
Related
Recommended

Simple Arduino Music Box: Envelopes

jc2048
jc2048
31 May 2018
image

Simple Music Maker

Enter Your Electronics & Design Project for Your Chance to Win a Fog Machine and a $100 Shopping Cart!

Back to The Project14 homepage image

Project14 Home
Monthly Themes
Monthly Theme Poll

 

Where, like a sweet melodious bird, it sung
Sweet varied notes, enchanting every ear!

 

Marcus Andronicus. Titus Andronicus [III, 1]. William Shakespeare.

 

Previous blogs:

A Simple Arduino Music Box

Simple Arduino Music Box: Voices

Simple Arduino Music Box: Chords

 

Introduction

 

With what I've done so far, the note produced by the music box maintains the same amplitude all the way through the note.

That's quite unrealistic. With a real instrument there's always a period at the start where the sound builds in intensity,

even if it's very fast, and a decline at the end as the energy in the system gradually dissipates. The shape of the

amplitude as it varies over time - what you'd see if you displayed the whole note on an oscilloscope - is called the

envelope. The period at the start, as the sound increases, is called the 'attack' and the period at the end, as it declines,

is the 'decay'.

 

If I want a better sounding note, I need to find a way to generate the envelope. One possibility is to do it in the

software. Another is to do it externally in the hardware. In either case, what we are trying to implement is a multiply -

we'll be multiplying the note values by the value for the envelope. There are some differences between the two approaches,

hardware and software. Doing it in software will result in a dramatic loss in resolution at the lower levels whereas doing

it externally in hardware will preserve the note shape. How much that matters I've got no idea, but doing it in software is

much simpler and easier to try so that's where I'll start.

 

Multiplication

 

Originally, I assumed that the microcontroller wouldn't be able to do a multiply within the time between interrupts -

multiplying with a software library is time consuming -  however, my assumption was wrong - looking at the microcontroller's

datasheet I realised that it has a built-in 8-bit hardware muliplier that can produce a result in only two instruction

cycles.

 

image

 

The actual instruction I'm trying for is MULSU which multiplies a signed 8-bit value by an unsigned 8-bit value for a signed

16-bit result.

 

image

 

The signed operand will be the waveform data and the unsigned one will be the envelope value at that moment in time. The

result I'll mangle a bit to give an 8-bit unsigned value to send to the DAC port.

 

First problem, though, is that I'm programming it in C, and not assembler, so how do I make the compiler use the multiply

instruction I want? Is it clever enough to use the instruction when appropriate or will it always use some multiply routine

instead. I'm going to try casting the operands and the result to the appropriate forms and look at the timing to see what is

happening. Much to my surprise, that seems to work - I have an envelope and it still completes easily within the timer

interval.

 

Reworking the Wave Table

 

The second issue is that the waveform data is currently 16-bit unsigned. I could manipulate that, but I've chosen instead to

rework the code that generates the waveform table so that the table contains signed char values that can be directly fed

to the multiply. Here's the code for that.

 

 

 

<stdio.h>

 <conio.h>

 <string.h>

 <dos.h>

 <errno.h>

 <math.h>

 temp_string[256];

 char waveTable[512];

 floatWaveTable[512];


 harmonicTable[10] = {1.0,0.0,0.9,0.0,0.8,0.0,0.0,0.0,0.0,0.0};


 scaleFactor = 1.0;

 maxValue = 0.0;


 main(int argc,char *argv[])

int i,j,temp;

/* print banner */

"\n--- waveTable DOS UTILITY PROGRAM V1.0 ---\n");

"Builds wave table for Arduino Music Box blog.\n");

/* generate float wave table in array */

for(i=0;i<512;i++) {

for(j=0;j<10;j++) {

for(i=0;i<512;i++) {

/* find maximum value and determine scale factor */

for(i=0;i<512;i++) {

if(floatWaveTable[i] > maxValue)

/* convert to int wave table in array */

for(i=0;i<512;i++) {

if( ((floatWaveTable[i] * scaleFactor)) >= 0.0)

unsigned char) ((floatWaveTable[i] * scaleFactor));

else

unsigned char) (floatWaveTable[i] * scaleFactor) + 0xff;

/* open output file */

if((handle=fopen("waveTable.txt","wt"))==NULL) {

"Failed to open output file.\n");

else {

/* write file banner */

"//\n");

"//  --- Wave table - generated by waveTable.exe \n");

"//\n");

"signed char waveTable[512] = {");

/* write table to file */

"\n");

for (i=0;i<32;i++) {

"   ");

for(j=0;j<16;j++) {

"0x%02x",waveTable[(i*16) + j]);

if(j<15)

",");

else {

if(i==31)

"};\n");

else

",\n");

/* close output file */

/* open output .csv file */

if((handle2=fopen("waveTable.csv","wt"))==NULL) {

"Failed to open .csv output file.\n");

else {

/* write table to file */

for (i=0;i<512;i++) {

"%i,%i,\n",i,waveTable[i]);

/* close output file */

"Done.\n");

 

 

One difference now is that if you graph the values with a speadsheet program it will look something like this - the left

half is the positive values (coming up from zero) and the right half is the negative values coming down from the maximum

value (which is one less than zero, ie -1).

 

image

 

Arduino Sketch

 

Here's the sketch for the Arduino Uno (keep in mind that I'm almost certainly throwing away portability and it might

need a fair bit of work to function on other Arduino platforms). I've changed to Yankee Doodle for the tune -  I figured you

were all probably fed up with Twinkle, Twinkle by now.

 

If your browser doesn't show it properly, the include at the start is of <Arduino.h>

 

/* Arduino Music Box with envelope generation */
#include <Arduino.h>
unsigned char noteState = 0;
unsigned char preScale = 0;
unsigned char envelopeValue = 0;
signed char waveValue = 0;
unsigned int tableOffset = 0;          // wave table index
unsigned int noteTime = 0xc35;         // note duration count
volatile unsigned int tableStep = 0;
int i=0;
unsigned int tuneNotes[] = {
    216,0x8000,
//    
    288,0x8000,
    288,0x8000,
    324,0x8000,
    363,0x4000,
//
    288,0x8000,
    363,0x8000,
    324,0x8000,
    272,0x8000,
//
    288,0x8000,
    288,0x8000,
    324,0x8000,
    363,0x8000,
//
    288,0xffff,
    272,0x8000,
    216,0x4000,
    192,0x4000,
//
    288,0x8000,
    288,0x8000,
    324,0x8000,
    363,0x8000,
//    
    385,0x8000,
    363,0x8000,
    324,0x8000,
    288,0x8000,
//    
    272,0x8000,
    216,0x8000,
    242,0x8000,
    272,0x8000,
//    
    288,0xffff,
    288,0x8000,
//    
    242,0xc000,
    272,0x4000,
    242,0x8000,
    216,0x8000,
//    
    242,0x8000,
    272,0x8000,
    288,0xffff,
//    
    216,0xc000,
    242,0x4000,
    216,0x8000,
    192,0x8000,
//    
    182,0x8000,
    192,0x8000,
    216,0xffff,
    
    0,0
    };
//
//  --- Wave table - generated by waveTable.exe 
//
signed char waveTable[512] = {
   0x00,0x06,0x0c,0x12,0x18,0x1e,0x24,0x2a,0x2f,0x35,0x3a,0x40,0x45,0x4a,0x4f,0x53,
   0x58,0x5c,0x60,0x64,0x67,0x6b,0x6e,0x71,0x73,0x75,0x78,0x79,0x7b,0x7c,0x7d,0x7e,
   0x7e,0x7e,0x7e,0x7e,0x7e,0x7d,0x7c,0x7b,0x79,0x78,0x76,0x74,0x71,0x6f,0x6c,0x6a,
   0x67,0x64,0x61,0x5e,0x5b,0x57,0x54,0x51,0x4d,0x4a,0x46,0x43,0x3f,0x3c,0x39,0x35,
   0x32,0x2f,0x2c,0x29,0x26,0x23,0x20,0x1e,0x1c,0x19,0x17,0x15,0x13,0x12,0x10,0x0f,
   0x0e,0x0d,0x0c,0x0c,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0c,0x0c,0x0d,0x0e,0x0f,0x10,
   0x11,0x13,0x14,0x15,0x17,0x19,0x1a,0x1c,0x1e,0x1f,0x21,0x23,0x25,0x26,0x28,0x2a,
   0x2b,0x2d,0x2f,0x30,0x31,0x33,0x34,0x35,0x36,0x37,0x38,0x38,0x39,0x39,0x3a,0x3a,
   0x3a,0x3a,0x3a,0x39,0x39,0x38,0x38,0x37,0x36,0x35,0x34,0x33,0x31,0x30,0x2f,0x2d,
   0x2b,0x2a,0x28,0x26,0x25,0x23,0x21,0x1f,0x1e,0x1c,0x1a,0x19,0x17,0x15,0x14,0x13,
   0x11,0x10,0x0f,0x0e,0x0d,0x0c,0x0c,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0c,0x0c,0x0d,
   0x0e,0x0f,0x10,0x12,0x13,0x15,0x17,0x19,0x1c,0x1e,0x20,0x23,0x26,0x29,0x2c,0x2f,
   0x32,0x35,0x39,0x3c,0x3f,0x43,0x46,0x4a,0x4d,0x51,0x54,0x57,0x5b,0x5e,0x61,0x64,
   0x67,0x6a,0x6c,0x6f,0x71,0x74,0x76,0x78,0x79,0x7b,0x7c,0x7d,0x7e,0x7e,0x7e,0x7f,
   0x7e,0x7e,0x7d,0x7c,0x7b,0x79,0x78,0x75,0x73,0x71,0x6e,0x6b,0x67,0x64,0x60,0x5c,
   0x58,0x53,0x4f,0x4a,0x45,0x40,0x3a,0x35,0x2f,0x2a,0x24,0x1e,0x18,0x12,0x0c,0x06,
   0x00,0xf9,0xf3,0xed,0xe7,0xe1,0xdb,0xd5,0xd0,0xca,0xc5,0xbf,0xba,0xb5,0xb0,0xac,
   0xa7,0xa3,0x9f,0x9b,0x98,0x94,0x91,0x8e,0x8c,0x8a,0x87,0x86,0x84,0x83,0x82,0x81,
   0x81,0x81,0x81,0x81,0x81,0x82,0x83,0x84,0x86,0x87,0x89,0x8b,0x8e,0x90,0x93,0x95,
   0x98,0x9b,0x9e,0xa1,0xa4,0xa8,0xab,0xae,0xb2,0xb5,0xb9,0xbc,0xc0,0xc3,0xc6,0xca,
   0xcd,0xd0,0xd3,0xd6,0xd9,0xdc,0xdf,0xe1,0xe3,0xe6,0xe8,0xea,0xec,0xed,0xef,0xf0,
   0xf1,0xf2,0xf3,0xf3,0xf4,0xf4,0xf4,0xf4,0xf4,0xf4,0xf3,0xf3,0xf2,0xf1,0xf0,0xef,
   0xee,0xec,0xeb,0xea,0xe8,0xe6,0xe5,0xe3,0xe1,0xe0,0xde,0xdc,0xda,0xd9,0xd7,0xd5,
   0xd4,0xd2,0xd0,0xcf,0xce,0xcc,0xcb,0xca,0xc9,0xc8,0xc7,0xc7,0xc6,0xc6,0xc5,0xc5,
   0xc5,0xc5,0xc5,0xc6,0xc6,0xc7,0xc7,0xc8,0xc9,0xca,0xcb,0xcc,0xce,0xcf,0xd0,0xd2,
   0xd4,0xd5,0xd7,0xd9,0xda,0xdc,0xde,0xe0,0xe1,0xe3,0xe5,0xe6,0xe8,0xea,0xeb,0xec,
   0xee,0xef,0xf0,0xf1,0xf2,0xf3,0xf3,0xf4,0xf4,0xf4,0xf4,0xf4,0xf4,0xf3,0xf3,0xf2,
   0xf1,0xf0,0xef,0xed,0xec,0xea,0xe8,0xe6,0xe3,0xe1,0xdf,0xdc,0xd9,0xd6,0xd3,0xd0,
   0xcd,0xca,0xc6,0xc3,0xc0,0xbc,0xb9,0xb5,0xb2,0xae,0xab,0xa8,0xa4,0xa1,0x9e,0x9b,
   0x98,0x95,0x93,0x90,0x8e,0x8b,0x89,0x87,0x86,0x84,0x83,0x82,0x81,0x81,0x81,0x80,
   0x81,0x81,0x82,0x83,0x84,0x86,0x87,0x8a,0x8c,0x8e,0x91,0x94,0x98,0x9b,0x9f,0xa3,
   0xa7,0xac,0xb0,0xb5,0xba,0xbf,0xc5,0xca,0xd0,0xd5,0xdb,0xe1,0xe7,0xed,0xf3,0xf9};
void setup() {
  
  // set the digital pins as outputs:
  pinMode(0, OUTPUT);
  pinMode(1, OUTPUT);
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
  pinMode(6, OUTPUT);
  pinMode(7, OUTPUT);
 
  pinMode(8, OUTPUT);     // for debug
  // timer 2 set up
  cli();                  // disable interrupts
  TCCR2A = 0;             // control register all 0
  TCCR2B = 0;             // control register all 0
  TCNT2 = 0;              // set count to 0
  OCR2A = 159;            // period = 160 x 1/16MHz = 10uS
  TCCR2A |= (1 << WGM21); // mode is clear on match
  TCCR2B |= (1 << CS20);  // no prescaler   
  TIMSK2 |= (1 << OCIE2A);  // enable interrupt on match
  sei();                  // enable interrupts
}
ISR(TIMER2_COMPA_vect) {
  PORTB |= 0x01;
  switch(noteState) {
    case 0:                           // inter-note gap
      PORTD = 0x80;
      noteTime = noteTime - 1;
      if(noteTime==0) {
        tableStep = tuneNotes[i++];
        noteTime = tuneNotes[i++] - 0x16e9;
        tableOffset = 0;
        envelopeValue = 0;
        preScale = 3;
        noteState = 1;
        }
      break;
    case 1:                           // attack
      tableOffset = tableOffset + tableStep;
      waveValue = (waveTable[tableOffset >> 7]);
      PORTD = (((signed int)(waveValue * envelopeValue)) >> 8) + 0x80;
      preScale--;
      if(preScale==0) {
        preScale = 3;
        if(envelopeValue != 255) {
          envelopeValue++;
          }
        else {
          noteState = 2;
          }
        }
      break;
    case 2:                           // hold
      tableOffset = tableOffset + tableStep;
      waveValue = (waveTable[tableOffset >> 7]);
      PORTD = (((signed int)(waveValue * envelopeValue)) >> 8) + 0x80;
      if(noteTime==0) {
        preScale = 20;
        noteState = 3;
        }
      else {
        noteTime = noteTime - 1;
        }
      break;
    case 3:                           // decay
      tableOffset = tableOffset + tableStep;
      waveValue = (waveTable[tableOffset >> 7]);
      PORTD = (((signed int)(waveValue * envelopeValue)) >> 8) + 0x80;
      preScale--;
      if(preScale==0) {
        preScale = 20;
        if(envelopeValue != 0) {
          envelopeValue--;
          }
        else {
          tableOffset = 0;
          tableStep = 0;
          noteTime = 0xc35;
          if (tuneNotes[i]==0)
            i=0;
          noteState = 0;
          }
        }
      break;
    }
         
  PORTB &= 0xFE; 
}
void loop() {
}

 

Waveforms

 

Here's a scope trace showing the end of one note and the start of the next:

 

image

 

Here's the start (the 'attack') in more detail.

 

image

 

Here's the end (the 'decay') in more detail. That's fine too. So, it looks like I've gotten all the signs and the

casting right.

 

image

 

My envelope generator isn't very sophisticated - just a ramp up and a ramp down - but I think you can probably see that the

values could come out of a table and give any envelope shape you wanted. I'll leave that as 'an exercise for the reader' (as

all the lazy writers say).

 

The Envelope Generator in Action

 

Finally here's a performance of part of Yankee Doodle (with some wrong notes - you'll have to correct them yourself in the

sketch and finish it off if you want to use it) played on the amazing £2 Arduino Music Box (might be a little more if you don't have an old

loudspeaker you can repurpose). My assistant still isn't impressed - this time he's got the rocket ready for a quick getaway

in case it all gets too much.

 

You don't have permission to edit metadata of this video.
Edit media
x
image
Upload Preview
image

 

 

 

There's still a bit too much noise. That results from variety of sources. Firstly, there's noise from the sample reconstruction.

That comes from working only 8 bits, the imperfect filtering, and the fact that there's another interrupt going on that's throwing

the position of the samples around. There's also a bit of noise resulting from the crossover distortion of my crude and

somewhat simple amplifier. Finally, the loudspeaker isn't very good - the frequency response isn't very good, there's a

horrible resonance on one of the notes, and there's a slight scratchy sound if I move the cone back and forwards which suggests

that it's seen much better days - I'll have a search and see if I can locate a better one.

 

Next blog:

Simple Arduino Music Box: Chimes

  • Sign in to reply

Top Comments

  • shabaz
    shabaz over 7 years ago +4
    It sounds really good compared to some toys! I see what you mean about the faint noise, although it could easily be assumed to be mechanical or motor-related noise in a toy : ) Your work reminded me of…
  • jc2048
    jc2048 over 7 years ago in reply to shabaz +4
    You asked for FM and you get, er, this... players.brightcove.net/.../index.html Sound quality of the clip isn't too good - the scope fan makes almost as much noise as the speaker. I don't know if it's…
  • genebren
    genebren over 7 years ago +3
    Jon, Nice update on your project. Electronic music is a very interesting field, as it can encompass so much math, electronics and physics. I have been interested in electronic music since I started in…
Parents
  • shabaz
    shabaz over 7 years ago

    It sounds really good compared to some toys! I see what you mean about the faint noise, although it could easily be assumed to be mechanical or motor-related noise in a toy : )

    Your work reminded me of this: Sound Synthesis and the Billion Dollar Paper

    I had a fun time working on that, but I had it way easier with a BeagleBone Black, i.e. not memory/processor constrained. I don't know if it is possible, but if anyone can pull off FM synthesis on the Arduino it is probably you.

    Awesome work on this Arduino project series : )

    • Cancel
    • Vote Up +4 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • jc2048
    jc2048 over 7 years ago in reply to shabaz

    You asked for FM and you get, er, this...

     

    You don't have permission to edit metadata of this video.
    Edit media
    x
    image
    Upload Preview
    image

     

    Sound quality of the clip isn't too good - the scope fan makes almost as much noise as the speaker.

     

    I don't know if it's correct for FM synthesis - I just slapped the code together and haven't looked seriously at the numbers it's producing - but it makes nice, interesting noises. The wavetable has a pure sinewave in it now, so all the timbral qualities come from the modulation. So far, it mostly sounds like a pipe organ. By moving the modulation frequency away from that of the tone, it's like selecting different drawbars.

     

    It's very easy to do with the code. First a look-up for the modulation, then the result gets added to the step size for the tone look-up. Took a couple of minutes to get it going.

     

    It's going to be interesting to experiment with, so thank you for the suggestion - I wouldn't have thought of doing that myself.

    • Cancel
    • Vote Up +4 Vote Down
    • Sign in to reply
    • More
    • Cancel
Comment
  • jc2048
    jc2048 over 7 years ago in reply to shabaz

    You asked for FM and you get, er, this...

     

    You don't have permission to edit metadata of this video.
    Edit media
    x
    image
    Upload Preview
    image

     

    Sound quality of the clip isn't too good - the scope fan makes almost as much noise as the speaker.

     

    I don't know if it's correct for FM synthesis - I just slapped the code together and haven't looked seriously at the numbers it's producing - but it makes nice, interesting noises. The wavetable has a pure sinewave in it now, so all the timbral qualities come from the modulation. So far, it mostly sounds like a pipe organ. By moving the modulation frequency away from that of the tone, it's like selecting different drawbars.

     

    It's very easy to do with the code. First a look-up for the modulation, then the result gets added to the step size for the tone look-up. Took a couple of minutes to get it going.

     

    It's going to be interesting to experiment with, so thank you for the suggestion - I wouldn't have thought of doing that myself.

    • Cancel
    • Vote Up +4 Vote Down
    • Sign in to reply
    • More
    • Cancel
Children
No Data
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2025 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube