diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..412eeda --- /dev/null +++ b/.gitattributes @@ -0,0 +1,22 @@ +# Auto detect text files and perform LF normalization +* text=auto + +# Custom for Visual Studio +*.cs diff=csharp +*.sln merge=union +*.csproj merge=union +*.vbproj merge=union +*.fsproj merge=union +*.dbproj merge=union + +# Standard to msysgit +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ebd21a --- /dev/null +++ b/.gitignore @@ -0,0 +1,163 @@ +################# +## Eclipse +################# + +*.pydevproject +.project +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.classpath +.settings/ +.loadpath + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# CDT-specific +.cproject + +# PDT-specific +.buildpath + + +################# +## Visual Studio +################# + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results +[Dd]ebug/ +[Rr]elease/ +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.vspscc +.builds +*.dotCover + +## TODO: If you have NuGet Package Restore enabled, uncomment this +#packages/ + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf + +# Visual Studio profiler +*.psess +*.vsp + +# ReSharper is a .NET coding add-in +_ReSharper* + +# Installshield output folder +[Ee]xpress + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish + +# Others +[Bb]in +[Oo]bj +sql +TestResults +*.Cache +ClientBin +stylecop.* +~$* +*.dbmdl +Generated_Code #added for RIA/Silverlight projects + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML + + + +############ +## Windows +############ + +# Windows image file caches +Thumbs.db + +# Folder config file +Desktop.ini + + +############# +## Python +############# + +*.py[co] + +# Packages +*.egg +*.egg-info +dist +build +eggs +parts +bin +var +sdist +develop-eggs +.installed.cfg + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +.coverage +.tox + +#Translations +*.mo + +#Mr Developer +.mr.developer.cfg + +# Mac crap +.DS_Store diff --git a/README b/README new file mode 100644 index 0000000..fbcb697 --- /dev/null +++ b/README @@ -0,0 +1,3 @@ +Arduino library for asynchronous playback of PCM/WAV files direct from SD card + +Details at tmrh20.blogspot.com \ No newline at end of file diff --git a/TMRpcm.cpp b/TMRpcm.cpp new file mode 100644 index 0000000..9ba926d --- /dev/null +++ b/TMRpcm.cpp @@ -0,0 +1,236 @@ +/*Library by TMRh20 2012*/ + +#include +#include +#include + + + + +const int buffSize = 150; //note: there are 2 sound buffers. This will require (soundBuff*2) memory free + +volatile byte buffer[2][buffSize+1]; +volatile boolean buffEmpty[2] = {false,false}; +volatile boolean whichBuff = false; +volatile int buffCount = 0; +volatile int volModMax = 1; +volatile int loadCounter = 0; +volatile boolean srX2 = false; +volatile boolean srX15 = false; +volatile boolean r12Toggle = false; +boolean paused = 0; +boolean playing = 0; + +int volMod = 3; + +File sFile; + + +TMRpcm::TMRpcm(){ + speakerPin = 11; + SAMPLE_RATE = 16000; + pwmMode = 1; +} + + +void TMRpcm::play(char* filename){ + + pinMode(speakerPin, OUTPUT); + stopPlayback(); paused = 0; + Serial.print("Playing: ");Serial.println(filename); + + if(!wavInfo(filename) ){ return; }//verify its a valid wav file + if(sFile){sFile.close();} + sFile = SD.open(filename); + + if(sFile){ + +// wavInfo(filename); //verify its a valid wav file + + sFile.seek(44); //skip the header info + for(int i=0; i 22000){ + Serial.print(" SampleRate Too High: "); + Serial.println(dVar); SAMPLE_RATE = 22000; + Serial.println("Setting SR to 22000"); + }else{ + SAMPLE_RATE = dVar; // Set the sample rate according to the file + } + //Serial.print("Sample Rate: "); Serial.println(dVar); + + //verify that Bits Per Sample is 8 (0-255) + xFile.seek(34); dVar = xFile.read(); + dVar = xFile.read() << 8 | dVar; + if(dVar != 8){Serial.print("Incorrect Bits Per Sample: "); Serial.println(dVar); xFile.close(); return 0;} + //Serial.print(" Bits Per Sample: "); Serial.println(dVar); + xFile.close(); return 1; +} + + + + +ISR(TIMER1_CAPT_vect){ + + // The first step is to disable this interrupt before manually enabling global interrupts. + // This allows this interrupt vector (COMPB) to continue loading data while allowing the overflow interrupt + // to interrupt it. ( Nested Interrupts ) + + TIMSK1 &= ~_BV(ICIE1); + + //Now enable global interupts before this interrupt is finished, so the music can interrupt the buffering + sei(); + + if(sFile.available() < buffSize){ + playing = 0; + if(sFile){sFile.close();} + TIMSK1 &= ~( _BV(ICIE1) | _BV(TOIE1) ); + } + + if(buffEmpty[0]){ + for(int i=0; i= buffSize){ + + buffCount = 0; + buffEmpty[whichBuff] = true; + whichBuff = !whichBuff; + + } + + if(buffCount == buffSize){ + buffCount = 0; whichBuff = !whichBuff; + buffEmpty[!whichBuff] = true; + } +} + + +void TMRpcm::startPlayback(){ + playing = 1; + + if(SAMPLE_RATE <= 10000){SAMPLE_RATE = SAMPLE_RATE*2; srX2 = true;}else{srX2 = false;} + if(SAMPLE_RATE > 10000 && SAMPLE_RATE < 13250){SAMPLE_RATE = SAMPLE_RATE*1.5; srX15 = true;}else{srX15 = false;} + +// unsigned int resolution = (8 * (1000000/SAMPLE_RATE)); //FastPWM mode only counts up, so we get higher resolution + unsigned int modeMultiplier = 0; + boolean pMode = 1; + if(pMode){modeMultiplier = 8;}else{modeMultiplier = 4;} + + //int test = 4 * (1000000/SAMPLE_RATE); + unsigned int resolution = modeMultiplier * (1000000/SAMPLE_RATE); //Serial.println(resolution); + volModMax = (resolution * 1.5) / 248 ; //no more than 75% PWM duty cycle + if(volMod > volModMax){ volMod = volModMax; } + //volMod = volModMax-1; + volMod = 1; + noInterrupts(); + ICR1 = resolution; + OCR1A = 1; + + if(pMode){ + TCCR1A = _BV(WGM11) | _BV(COM1A1); //WGM11,12,13 all set to 1 = fast PWM/w ICR TOP + TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS10); + }else{ + TCCR1A = _BV(COM1A1); + TCCR1B = _BV(WGM13) | _BV(CS10); + } + //TIMSK1 &= ~_BV(OCIE1A); + TIMSK1 = ( _BV(ICIE1) | _BV(TOIE1) ); + interrupts(); + + } + + + + +void TMRpcm::stopPlayback(){ + playing = 0; + if(sFile){sFile.close();} + TIMSK1 &= ~( _BV(ICIE1) | _BV(TOIE1) ); + +} + +void TMRpcm::disable(){ + playing = 0; + if(sFile){sFile.close();} + TIMSK1 &= ~( _BV(ICIE1) | _BV(TOIE1) ); + TCCR1B &= ~(_BV(CS10) | _BV(CS11) | _BV(CS12)); + +} + + +boolean TMRpcm::isPlaying(){ + return playing; +} diff --git a/TMRpcm.h b/TMRpcm.h new file mode 100644 index 0000000..af3a44c --- /dev/null +++ b/TMRpcm.h @@ -0,0 +1,33 @@ +/*Library by TMRh20 2012*/ + +#include + + + +#ifndef tmrPCM_h +#define tmrPCM_h + + +class TMRpcm +{ + public: + TMRpcm(); + void play(char* filename); + void stopPlayback(); + void volume(int vol); + void disable(); + void pause(); + boolean wavInfo(char* filename); + int speakerPin; + boolean pwmMode; + boolean isPlaying(); + + private: + void startPlayback(); + int SAMPLE_RATE; + + +}; + + +#endif \ No newline at end of file diff --git a/examples/music/music.ino b/examples/music/music.ino new file mode 100644 index 0000000..90aceb1 --- /dev/null +++ b/examples/music/music.ino @@ -0,0 +1,57 @@ +#include // need to include the SD library +//#define SD_ChipSelectPin 53 //example uses hardware SS pin 53 on Mega2560 +#define SD_ChipSelectPin 4 //using digital pin 4 on arduino nano 328 +#include // also need to include this library... + +TMRpcm tmrpcm; // create an object for use in this sketch + +unsigned long time = 0; + +void setup(){ + + tmrpcm.speakerPin = 9; //11 on Mega, 9 on Uno, Nano, etc + + Serial.begin(115200); + pinMode(A0,OUTPUT); //LED Connected to analog pin 0 + if (!SD.begin(SD_ChipSelectPin)) { // see if the card is present and can be initialized: + Serial.println("SD fail"); + return; // don't do anything more if not + + } + else{ + Serial.println("SD ok"); + } + tmrpcm.play("music"); //the sound file "music" will play each time the arduino powers up, or is reset +} + + + +void loop(){ + + //blink the LED manually to demonstrate music playback is independant of main loop + if(tmrpcm.isPlaying() && millis() - time > 50 ) { + digitalWrite(A0,!digitalRead(A0)); + time = millis(); + }else + if(millis() - time > 500){ + digitalWrite(A0,!digitalRead(A0)); + time = millis(); + } + + + if(Serial.available()){ + switch(Serial.read()){ + case 'd': tmrpcm.play("music"); break; + case 'P': tmrpcm.play("temple"); break; + case 't': tmrpcm.play("catfish"); break; + case 'p': tmrpcm.pause(); break; + case '?': if(tmrpcm.isPlaying()){ Serial.println("A wav file is being played");} break; + case 'm': tmrpcm.pwmMode = !tmrpcm.pwmMode; break; + case 'S': tmrpcm.stopPlayback(); break; + case '=': tmrpcm.volume(1); break; + case '-': tmrpcm.volume(0); break; + default: break; + } + } + +} \ No newline at end of file diff --git a/keywords.txt b/keywords.txt new file mode 100644 index 0000000..b030a23 --- /dev/null +++ b/keywords.txt @@ -0,0 +1,10 @@ +TMRpcm KEYWORD1 +play KEYWORD2 +stopPlayback KEYWORD2 +volume KEYWORD2 +disable KEYWORD2 +pause KEYWORD2 +wavInfo KEYWORD2 +speakerPin KEYWORD2 +pwmMode KEYWORD2 +isPlaying KEYWORD2