Newbie Spin Question
NWCCTV
Posts: 3,629
Being new to Spin I am not even sure if this is possible so go easy on me. If it is not possible suggestions are welcome. I have modified the Pressure Sensor Bar Graph Demo program to start and stop various servos depending on the rawReading received. Now I need tp play some .wav files pretty much at the same time. If I include one of the .wav/sd card reading programs from the OBEX in my Objects section, is there a way to call the "read SD card and Play .wav file functions from that program?

Comments
obj
music : "name_of_object_here"
then in your program you can call any of its public methods like this:
music.name_of_method
Edit: Here is the .wav/sd card code:
' ASM WAV Player Ver. 2g (Plays 16-bit PCM WAV files from SD card) ' Copyright 2007-2013 Raymond Allen ' MIT License: See end of file for terms of use. ' Settings for Demo Board Audio Output: Right Pin# = 10, Left Pin# = 11 ' Rev.2G: Added support for variable size header before "data" ' Rev.2C: Made code easier to use and understand and added mono support CON 'Crystal settings _clkmode = xtal1 + pll16x _xinfreq = 5_000_000 '80 MHz CON 'SD card pin settings 'Set the pins used for SD card here 'Propeller Platform Express settings: SD_MISO=22 SD_CLK=23 SD_MOSI=24 SD_CS=25 'Quick Media Player Settings { SD_MISO=27 SD_CLK=26 SD_MOSI=25 SD_CS=24 } CON 'Audio Pins and buffer size 'Here you can select the audio output pins 'Demo Board Settings: Pin_Right=26 Pin_Left=27 'Quick Media Player Settings { Pin_Right=23 Pin_Left=22 } 'Slow SD cards may require a larger buffer, adjust with value below buffLongs=100 buffWords = buffLongs*2+1 'Volume control volume = 45535 'range is 0 to 65535 with 65535 being the loudest VAR long parameter1 'to pass @buff1 to ASM long parameter2 'to pass @buff2 to ASM long parameter3 'to pass sample rate to ASM long parameter4 'to pass #samples to ASM long parameter5 'to pass #channels to ASM long parameter6 'to pass #samples in each buffer long parameter7 'to pass volume to ASM word buff1[buffWords] word buff2[buffWords] byte Header[44] OBJ SD : "FSRW" ser : "fullduplexserial" 'for monitoring PUB Main 'Play a WAV File 'Start serial connection ser.start(31,30,0,9600) waitcnt(cnt+clkfreq*2) 'Wait 2 seconds (but don't need to) ser.str(string(13,13,"Playing wav file...",13)) PlayWav(string("heart1.wav")) ' <--------- Change .wav filename to play here !!!!!!!!!!!!!! ser.str(STRING("Play wav complete.",13)) repeat 'all done PUB PlayWav(pFileName)|n,i,j, SampleRate,Samples,nChannels,ChunkSize,SamplePeriod,PlayTime 'open the WAV file (NOTE: Only plays stereo, 16-bit PCM WAV Files !!!!!!!!!!!!!) 'Mounting SD card now ser.str(string("Attempting to mount SD card",13)) repeat 'try forever to mount the SD card i:=\sd.Mount_Explicit(SD_MISO,SD_CLK,SD_MOSI,SD_CS) if i==0 quit 'mounted, so exit waitcnt(cnt+clkfreq/4) ser.tx(".") ser.str(string("SD card mounted.",13)) 'open file ser.str(string("Attempting to open wav file: ")) ser.str(pFileName) ser.tx(13) i:=\sd.popen(pFileName,"r") if (i<>0) ser.str(string("Cannot open wav file.",13)) return 'Plays only 16-bit PCM WAV Files !!!!!!!!!!!!!!!! 'See here for header format: [URL]https://ccrma.stanford.edu/courses/422/projects/WaveFormat/[/URL] i:=sd.pread(@Header, 44-8) 'read data words to input stereo buffer 'Get bits per sample (must be 16) if (Header[35]<<8+Header[34])<>16 ser.str(string(13,"Error: Not 16 bits per sample!",13)) return 'Get audio format, must be PCM if (Header[21]<<8+Header[20])<>1 ser.str(string(13,"Error: Not uncompressed PCM data",13)) return 'Get sample rate from header SampleRate:=Header[27]<<24+Header[26]<<16+Header[25]<<8+Header[24] ser.str(string("Sample Rate: ")) ser.dec(SampleRate) ser.tx(13) 'Get # channels from header nChannels:=Header[23]<<8+Header[22] ser.str(string("#Channels: ")) ser.dec(nChannels) ser.tx(13) 'Find data repeat sd.pread(@Header[36], 4) if Header[36]<<24+Header[37]<<16+Header[38]<<8+Header[39] == $64617461 quit 'found data sd.pread(@Header[40],4) 'read subchunk size 'get # samples from header ChunkSize:=Header[43]<<24+Header[42]<<16+Header[41]<<8+Header[40] Samples:=(ChunkSize>>1)/nChannels ser.str(string("#Samples: ")) ser.dec(Samples) ser.tx(13) 'Calculate play time SamplePeriod:=CLKFREQ/SampleRate '#clocks between samples'1814'for 44100ksps, 5000 'for 16ksps ser.str(string("Playtime (s) = ")) ser.dec(Samples*SamplePeriod/ClkFreq) ser.tx(13) 'Start ASM player in a new cog ser.str(STRING("Running ASM Player",13)) parameter1:=@buff1 'address of first buffer parameter2:=@buff2 'address of second buffer parameter3:=SamplePeriod 'time between samples parameter4:=Samples 'total # samples parameter5:=nChannels '#channels parameter6:=(BuffWords-1)/nChannels '#samples in each buffer parameter7:=volume&$FFFFF 'volume control (0..65535) COGNEW(@ASMWAV,@parameter1) 'Keep filling buffers until end of file ' note: using alternating buffers to keep data always at the ready... n:=buffWords-1 buff1[n]:=1 'set empty flags at end of buffers buff2[n]:=1 i:=n*2 'number of bytes to read 'Keep buffers filled until got all samples j:=samples*nChannels*2 PlayTime :=cnt repeat repeat until buff1[n]==1 'wait until buffer empty if sd.pread(@buff1, i)<>i 'read data words to input stereo buffer quit buff1[n]:=0 'clear empty flag j-=i if j=<0 quit repeat until buff2[n]==1 'wait until buffer empty if sd.pread(@buff2, i)<>i quit 'read data words to input stereo buffer buff2[n]:=0 'clear empty flag j-=i if j=<0 quit ser.str(string("Played time (s) = ")) ser.dec((cnt-PlayTime)/clkfreq) ser.tx(13) 'must have reached the end of the file, so close it \sd.pclose return DAT ORG 0 ASMWAV 'load input parameters from hub to cog given address in par movd :par,#pData1 mov x,par mov y,#7 'input 7 parameters :par rdlong 0,x add :par,dlsb add x,#4 djnz y,#:par setup 'setup output pins MOV DMaskR,#1 ROL DMaskR,OPinR OR DIRA, DMaskR MOV DMaskL,#1 ROL DMaskL,OPinL OR DIRA, DMaskL 'setup counters OR CountModeR,OPinR MOV CTRA,CountModeR OR CountModeL,OPinL MOV CTRB,CountModeL 'Wait for SPIN to fill table MOV WaitCount, CNT ADD WaitCount,BigWait WAITCNT WaitCount,#0 'setup loop table MOV LoopCount,SizeBuff MOV pData,pData1 MOV nTable,#1 'setup loop counter MOV WaitCount, CNT ADD WaitCount,dRate MainLoop CMP nSamples,#0 wz,wc 'see if out of samples IF_e JMP #Done SUB nSamples,#1 waitcnt WaitCount,dRate cmp nChannel,#2 wz,wc if_e jmp #Stereo 'otherwise, assume mono Mono call #ReadWithVolume mov right,y mov left,right jmp #Output Stereo call #ReadWithVolume mov right,y add pData,#2 call #ReadWithVolume mov left,y Output ADD pData,#2 MOV FRQA,Right MOV FRQB,Left 'loop DJNZ LoopCount,#MainLoop WRword one,pData 'set empty flag MOV LoopCount,SizeBuff 'switch table ? CMP nTable,#1 wz IF_Z JMP #SwitchToTable2 SwitchToTable1 MOV nTable,#1 MOV pData,pData1 JMP #MainLoop SwitchToTable2 MOV nTable,#2 MOV pData,pData2 JMP #MainLoop Done 'now stop COGID thisCog COGSTOP thisCog 'Multiply routine adapted from manual '' Multiply x[15..0] by y[15..0] (y[31..16] must be 0) ' on exit, product in y[31..0] ReadWithVolume mov x,dVolume rdword y,pData add y,twos and y,ffff shl x,#16 'get multiplicand into x[31..16] mov t,#16 'ready for 8 multiplier bits shr y,#1 wc 'get initial multiplier bit into c :loop if_c add y,x wc 'if c set, add multiplicand to product rcr y,#1 wc 'put next multiplier in c, shift prod. djnz t,#:loop 'loop until done ReadWithVolume_ret ret 'return with product in y[31..0] 'Working variables thisCog long 0 x long 0 y long 0 t long 0 dlsb long 1 << 9 BigWait long 100000 twos long $8000_8000 ffff long $ffff 'Loop parameters nTable long 0 WaitCount long 0 pData long 0 LoopCount long 0 Left long 0 Right long 0 Zero long 0 one long 1 'setup parameters DMaskR long 0 'right output mask OPinR long Pin_Right 'right channel output pin # DMaskL long 0 'left output mask OPinL long Pin_Left 'left channel output pin # CountModeR longobj music : " " 'in the quotes type the name of the object exactly as it is saved. 'Also make sure its in the same folder as this code. pub music.PlayWav(string("song")) 'replace song with the file on the sd card you would like played. leave the quotes!{{ Pressure Sensor and BarGraph DEMO.spin Program Date: 1/4/2013 Version: 1.1 Modified version to add the ability to turn a servo on and off at specified pressure. Description: This program reads voltage values from the Propeller BOE's ADC and prints the raw value to the Parallax Serial Terminal program running on your PC. The program also uses a cog to show a linear representation of the pressure sensed on a 10-segment LED bar graph. Propeller BOE Wiring Diagram === Pressure Sensor Pin Connections === +5v  │ ┌─────────────┐ │ │ │ │ │ MPX5010 │ │ │ │ │ └─┬─┬─┬─┬─┬─┬─┘ │ Pin 1 │ │ │ x x x │ AD0 ───┘ │ └─────────┘  GND === LED Bar Graph Pin Connections === P0 ──────┐ P1 ──────┫ P2 ──────┫ P3 ──────┫ P4 ──────┫ P5 ──────┫ P6 ──────┫ P7 ──────┫ P8 ──────┫ P9 ──────┫ 220Ω │ │  GND }} CON 'let the compiler know which crystal and PLL settings to use _xinfreq = 5_000_000 _clkmode = xtal1 + pll16x _leftServoPin = 14 _rightServoPin = 9 _timeStepInMilliseconds = 20 _updateFrequencyInHertz = 50 INPUT = false 'bit pattern is all 0s OUTPUT = true 'bit pattern is all 1s PC_BAUD = 115_200 'PC serial terminal's baud rate ADC_CH0 = 1 << 0 ADC_CH1 = 1 << 1 ADC_CH2 = 1 << 2 ADC_CH3 = 1 << 3 ADC_ALL = ADC_CH0 + ADC_CH1 + ADC_CH2 + ADC_CH3 'LED Bar graph pin definitions BAR_GRAPH_0 = 0 BAR_GRAPH_1 = 1 BAR_GRAPH_2 = 2 BAR_GRAPH_3 = 3 BAR_GRAPH_4 = 4 BAR_GRAPH_5 = 5 BAR_GRAPH_6 = 6 BAR_GRAPH_7 = 7 BAR_GRAPH_8 = 8 BAR_GRAPH_9 = 9 Servo_Run = 14 OBJ adc : "PropBOE ADC" 'Requires 1 cog pc : "Parallax Serial Terminal" 'Requires 1 cog system : "Propeller Board of Education" 'PropBOE configuration tools time : "Timing" 'Timekeeping and delay object servo : "PropBOE Servos" 'Servo control object music : "WavPlayer2g" ' SD Card/Wave Plaer VAR 'Globally accessible variables, shared by all cogs long pressure long cogStack[20] word cogStack2[20] PUB Go | rawReading 'Start other objects pc.Start(PC_BAUD) adc.Start(ADC_CH0) 'Launch additional cogs cognew(RunBarGraph, @cogStack) repeat rawReading := adc.In(0) 'Get the reading from channel 0 on the ADC. pressure := rawReading / 25 'Scale the raw reading to be displayed on the LED bar graph. 'Note that this scaled reading does not correspond with a particular 'unit of pressure such as mmH2O, mmHg, kPa, or PSI. Check the sensor's 'datasheet (MPX5010DP) for the mV/mmH2O conversion value if you want an 'absolute reading in a particular unit of pressure. ' 'The global variable "pressure" is shared between this cog and the cog 'that is controlling the LED bar graph. '===== Print to the PC serial terminal ===== pc.Home pc.Str(string("=== Pressure Sensor Test ===")) pc.NewLine pc.NewLine pc.Str(string("Raw ADC Value: ")) pc.dec(rawReading) pc.Chars(" ", 5) pc.NewLine if rawReading >= 150 ' Set rawReading accordingly to turn servo on servo.Set(14, 200) servo.Set(15, -200) dira[11] := 1 outa[11] := 1 ' Servo on pin 14 counterclockwise elseif rawReading <149 ' Set rawReading accordingly to stop servo servo.stop ' Stop servo dira[11] := 0 outa[11] := 0 waitcnt(cnt + clkfreq/20) 'wait for ~1/20th seconds so as not to flood the computer's serial port with data. PUB RunBarGraph | i dira[BAR_GRAPH_9..BAR_GRAPH_0] := OUTPUT 'set range of pins to output '(this works in this case because the pins are consecutive) repeat outa[BAR_GRAPH_9..BAR_GRAPH_0] := 1<<pressure - 1 'Continually set the value of the scaled pressure to the LED bar graph pins. 'Do a little bitwise manipulation to make the LEDs look nice. PUB music.PlayWav(string("chimes")) 'replace song with the file on the sd card you would like played. leave the quotes! DAT {{PUB music.PlayWav(string("chimes")) 'replace song with the file on the sd card you would like played. leave the quotes!You have to name the PUB function!
PUB Chimes music.PlayWav(string("chimes")) 'replace song with the file on the sd card you would like played. leave the quotes!electrodude
If your code looks like this now:
PUB Chimes music.PlayWav(string("chimes")) 'replace song with the file on the sd card you would like played. leave the quotes!Then in the part of your program you want the wav to play you just put a call to Chimes, ie just place the word "Chimes" without quotes there
The PUB and PRI methods sit there waiting to do their code in your program and only execute their code when called by your program.
PUB Chimes music.PlayWav(string("chimes.wav"))What you are showing is the method to play the wav file, which directs to Rayman's Wav Player method but you have to call this first by putting the word chimes somewhere else in the program.
The method PUB Chimes will not do anything until called.
Can you show the whole code and the part of your program where you wish the wav to play and I can then show you how to call the Chimes method.
{{ Pressure Sensor and BarGraph DEMO.spin Program Date: 1/4/2013 Version: 1.1 Modified version to add the ability to turn a servo on and off at specified pressure. Description: This program reads voltage values from the Propeller BOE's ADC and prints the raw value to the Parallax Serial Terminal program running on your PC. The program also uses a cog to show a linear representation of the pressure sensed on a 10-segment LED bar graph. Propeller BOE Wiring Diagram === Pressure Sensor Pin Connections === +5v  │ ┌─────────────┐ │ │ │ │ │ MPX5010 │ │ │ │ │ └─┬─┬─┬─┬─┬─┬─┘ │ Pin 1 │ │ │ x x x │ AD0 ───┘ │ └─────────┘  GND === LED Bar Graph Pin Connections === P0 ──────┐ P1 ──────┫ P2 ──────┫ P3 ──────┫ P4 ──────┫ P5 ──────┫ P6 ──────┫ P7 ──────┫ P8 ──────┫ P9 ──────┫ 220Ω │ │  GND }} CON 'let the compiler know which crystal and PLL settings to use _xinfreq = 5_000_000 _clkmode = xtal1 + pll16x _leftServoPin = 14 _rightServoPin = 9 _timeStepInMilliseconds = 20 _updateFrequencyInHertz = 50 INPUT = false 'bit pattern is all 0s OUTPUT = true 'bit pattern is all 1s PC_BAUD = 115_200 'PC serial terminal's baud rate ADC_CH0 = 1 << 0 ADC_CH1 = 1 << 1 ADC_CH2 = 1 << 2 ADC_CH3 = 1 << 3 ADC_ALL = ADC_CH0 + ADC_CH1 + ADC_CH2 + ADC_CH3 'LED Bar graph pin definitions BAR_GRAPH_0 = 0 BAR_GRAPH_1 = 1 BAR_GRAPH_2 = 2 BAR_GRAPH_3 = 3 BAR_GRAPH_4 = 4 BAR_GRAPH_5 = 5 BAR_GRAPH_6 = 6 BAR_GRAPH_7 = 7 BAR_GRAPH_8 = 8 BAR_GRAPH_9 = 9 Servo_Run = 14 OBJ adc : "PropBOE ADC" 'Requires 1 cog pc : "Parallax Serial Terminal" 'Requires 1 cog system : "Propeller Board of Education" 'PropBOE configuration tools time : "Timing" 'Timekeeping and delay object servo : "PropBOE Servos" 'Servo control object music : "WavPlayer2g" ' SD Card/Wave Plaer VAR 'Globally accessible variables, shared by all cogs long pressure long cogStack[20] PUB Go | rawReading 'Start other objects pc.Start(PC_BAUD) adc.Start(ADC_CH0) 'Launch additional cogs cognew(RunBarGraph, @cogStack) repeat rawReading := adc.In(0) 'Get the reading from channel 0 on the ADC. pressure := rawReading / 25 'Scale the raw reading to be displayed on the LED bar graph. 'Note that this scaled reading does not correspond with a particular 'unit of pressure such as mmH2O, mmHg, kPa, or PSI. Check the sensor's 'datasheet (MPX5010DP) for the mV/mmH2O conversion value if you want an 'absolute reading in a particular unit of pressure. ' 'The global variable "pressure" is shared between this cog and the cog 'that is controlling the LED bar graph. '===== Print to the PC serial terminal ===== pc.Home pc.Str(string("=== Pressure Sensor Test ===")) pc.NewLine pc.NewLine pc.Str(string("Raw ADC Value: ")) pc.dec(rawReading) pc.Chars(" ", 5) pc.NewLine if rawReading >= 150 ' Set rawReading accordingly to turn servo on servo.Set(14, 200) ' Servo on pin 14 counterclockwise servo.Set(15, -200) dira[11] :=1 outa[11] :=1 elseif rawReading <149 ' Set rawReading accordingly to stop servo servo.stop servo.stop dira[11] :=0 outa[11] :=0 ' Stop servo waitcnt(cnt + clkfreq/20) 'wait for ~1/20th seconds so as not to flood the computer's serial port with data. PUB Chimes | Chimes music.PlayWav(string("Chimes.wav")) PUB RunBarGraph | i dira[BAR_GRAPH_9..BAR_GRAPH_0] := OUTPUT 'set range of pins to output '(this works in this case because the pins are consecutive) repeat outa[BAR_GRAPH_9..BAR_GRAPH_0] := 1<<pressure - 1 'Continually set the value of the scaled pressure to the LED bar graph pins. 'Do a little bitwise manipulation to make the LEDs look nice. DAT {{CON ' ' first PUB encountered is considered to be the main program? ' PUB Go | rawReading 'Start other objects pc.Start(PC_BAUD) adc.Start(ADC_CH0) 'Launch additional cogs cognew(RunBarGraph, @cogStack) ' ' repeat forever - main loop of your program ' repeat rawReading := adc.In(0) 'Get the reading from channel 0 on the ADC. pressure := rawReading / 25 'Scale the raw reading to be displayed on the LED bar graph. if rawReading >= 150 ' Set rawReading accordingly to turn servo on servo.Set(14, 200) ' Servo on pin 14 counterclockwise servo.Set(15, -200) dira[11] :=1 outa[11] :=1 elseif rawReading <149 ' Set rawReading accordingly to stop servo servo.stop servo.stop dira[11] :=0 outa[11] :=0 ' Stop servo waitcnt(cnt + clkfreq/20) 'wait for ~1/20th seconds so as not to flood the computer's serial port with data. ' ' Execution stops right here.... loops back to the repeat ' It doesn't fall through to a Pub or PRI method. ' Chimes is never called. ' PUB Chimes | Chimes music.PlayWav(string("Chimes.wav"))WAV-Player Program // // Author: Kwabena W. Agyeman // Updated: 7/20/2011 // Designed For: P8X32A // Version: 1.2 // // Copyright (c) 2011 Kwabena W. Agyeman // See end of file for terms of use. // // Update History: // // v1.0 - Original release - 7/6/2011. // v1.1 - Tested with updated driver - 7/18/2011. // v1.2 - Added support for dither mode - 7/20/2011. // // Plays a WAV file over and over again at different sample rates. The WAV file is specified by its file path name string that // can be found in the code below. The WAV-Player program can play any standard WAV file. E.g. 16/8-Bit 16000/22050/44100-Hz. // // Nyamekye, /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// }} CON _clkmode = xtal1 + pll16x ' The clkfreq is 80MHz. _xinfreq = 5_000_000 ' Demo board compatible. _dopin = 22 _clkpin = 23 _dipin = 24 _cspin = 25 _cdpin = 4 ' -1 if unused. _wppin = 5 ' -1 if unused. _rtcres1 = -1 ' -1 always. _rtcres2 = -1 ' -1 always. _rtcres3 = -1 ' -1 always. _lpin = 26 ' -1 if unused. _rpin = 27 ' -1 if unused. _volume = 50 ' Default volume. _ditherEnable = true ' "true" or "false" please. _ditherLevel = 4 ' 0 = Most Dither ... 31 = Least Dither. OBJ dac: "WAV-Player_DACEngine" VAR long spinPlayerStack[100] PUB demo dac.FATEngineStart(_dopin, _clkpin, _dipin, _cspin, _wppin, _cdpin, _rtcres1, _rtcres2, _rtcres3) dac.DACEngineStart(constant(_lpin | (not(not(_ditherEnable)))), constant(_rpin | (not(not(_ditherEnable)))), _volume) ' Above Never fail - no need to check return value. if(_ditherEnable) dac.DACDitherEngineStart(_lpin, _rpin, _ditherLevel) ' Never fails - no need to check return value. cognew(spinPlayer, @spinPlayerStack) ' Startup separate process. repeat ' Repeat forever. repeat until(dac.wavePlayerState) ' Wait until start. repeat until(dac.fileSamplePosition > (dac.fileSampleNumber / 3)) ' Wait until 1/3rds in. dac.fileSampleRateSetup((dac.fileSampleRate * 4) / 3) ' Sample rate set to 4/3rds the original. repeat until(dac.fileSamplePosition > ((dac.fileSampleNumber / 3) * 2)) ' Wait until 2/3rds in. dac.fileSampleRateSetup((dac.fileSampleRate * 3) / 4) ' Sample rate set to 3/4ths the original. repeat while(dac.wavePlayerState) ' Wait until stop. PUB spinPlayer ' Plays a WAV file over and over again. 'repeat ' Forever dac.playWAVFile(string("chimes.wav")) ' Supports WAV files up to ~2GB. {{EDIT: if that is Kye's demo file posted above it shows how to start it. Make sure the chimes.wav is in the folder his engine wants to see it in - probably the folder the engine is in.
//
// Author: Kwabena W. Agyeman
// Updated: 7/20/2011
// Designed For: P8X32A
// Version: 1.3
//
// Copyright (c) 2011 Kwabena W. Agyeman
// See end of file for terms of use.
//
// Update History:
//
// v1.0 - Original release - 7/27/2010.
// v1.1 - Merged object with player - 7/6/2011.
// v1.2 - Fixed playback issue - 7/18/2011.
// v1.3 - Added dither cog - 7/20/2011.
//
// For each included copy of this object only one spin interpreter should access it at a time.
//
// Nyamekye,
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Audio Circuit:
//
// 100OHM 1UF
// Left Pin Number --R
C-- Left Out (Use better audio circuit filtering for noise issues).
// |
// C 10NF
// |
// GND
//
// 100OHM 1UF
// Right Pin Number --R
C-- Right Out (Use better audio circuit filtering for noise issues).
// |
// C 10NF
// |
// GND
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}}
OBJ fat: "SD-MMC_FATEngine.spin"
VAR
long leftValue, rightValue, samplesPerSecond, clocksPerSample, numberOfSamples, samplePosition, ditherLeft, ditherRight
word dataBlock[512], callerPointer, callePointer, leftVolume, rightVolume, numberOfChannels, bitsPerSample
byte stopedOrStarted, beginOrEnd, cogIdentification, ditherCogIdentification
PUB leftChannelOutput '' 3 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Returns the current sample going out to the left audio channel. The value returned is a 16 bit signed extended number.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
return leftValue
PUB rightChannelOutput '' 3 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Returns the current sample going out to the right audio channel. The value returned is a 16 bit signed extended number.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
return rightValue
PUB fileSampleRateSetup(sampleRate) '' 4 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Setups the sample rate of the audio file. The samples are played at the sample rate.
'' //
'' // SampleRate - The new sample rate to playback sample at. Between 1 - 66,150 samples per second.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
clocksPerSample := (clkfreq / ((sampleRate <# 66_150) #> 1))
PUB fileSampleRate '' 3 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Returns the sample rate of the audio file. The samples are played at the sample rate.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
return samplesPerSecond
PUB fileSampleNumber '' 3 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Returns the number of samples in the audio file. The samples are played at the sample rate.
'' //
'' // Valid while method "wavePlayerState" returns true.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
return numberOfSamples
PUB fileSamplePosition '' 3 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Returns the playback sample position in the audio file. The samples are played at the sample rate.
'' //
'' // Valid while method "wavePlayerState" returns true.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
return (numberOfSamples - samplePosition)
PUB leftChannelVolume(newVolume) '' 4 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Changes the volume of the left channel. (0 to 100).
'' //
'' // NewVolume - New volume to output at. Zero mutes the channel.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
leftVolume := (((newVolume <# 100) #> 0) * constant($1_00_00 / 100))
PUB rightChannelVolume(newVolume) '' 4 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Changes the volume of the right channel. (0 to 100).
'' //
'' // NewVolume - New volume to output at. Zero mutes the channel.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
rightVolume := (((newVolume <# 100) #> 0) * constant($1_00_00 / 100))
PUB pauseWavePlayer(state) '' 4 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Pause or unpause the wave player. The method "wavePlayerState" has nothing to do with the wave player being paused.
'' //
'' // State - New wave player state. True for paused and false for unpaused.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
stopedOrStarted := (not(not(state)))
PUB wavePlayerState '' 3 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Returns the wave player's state. True while playing a WAV file and false while not playing a WAV file.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
result or= beginOrEnd
PUB playWAVFile(filePathName) '' 56 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Plays a WAV file on the SD/MMC card - supports WAV files up to ~2GB and over ~1 second of audio.
'' //
'' // The WAV player supports 8-Bit 1/2 channel WAV files sampled between 1 Hz to 44,100 Hz.
'' //
'' // The WAV player supports 16-Bit 1/2 channel WAV files sampled between 1 Hz to 44,100 Hz.
'' //
'' // If an error occurs while reading the WAV file a string describing that error will be returned. Null otherwise.
'' //
'' // If an error occurs while reading the FAT file system a string describing that error will be returned. Null otherwise.
'' //
'' // FilePathName - A file system path string specifying the path of the WAV file to play.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
result := \playWAV(filePathName)
\fat.unmountPartition
PRI playWAV(filePathName) | chunkSkip, blockAlign, byteRate, searchChunkSize ' 52 Stack Longs
ifnot(fat.partitionMounted)
fat.mountPartition(0)
fat.openFile(filePathName, "R")
if(fat.readLong <> $46_46_49_52)
abort string("Not A RIFF File")
if((fat.readLong + 8) <> fat.fileSize)
abort string("RIFF Chunk Size Invalid")
if(fat.readLong <> $45_56_41_57)
abort string("Not A WAVE File")
repeat while(fat.readLong <> $20_74_6D_66)
searchChunkSize := fat.readLong
if((searchChunkSize < 1) or (fat.fileSize == fat.fileTell))
abort string("FMT Chunk Missing")
fat.fileSeek(searchChunkSize + fat.fileTell)
chunkSkip := (fat.readLong - 16)
if(chunkSkip < 0)
abort string("FMT Chunk Size Invalid")
if(fat.readShort <> 1)
abort string("Not A LPCM file")
numberOfChannels := fat.readShort
if((numberOfChannels < 1) or (2 < numberOfChannels))
abort string("Unsupported Number Of Channels")
samplesPerSecond := fat.readLong
if((samplesPerSecond < 1) or (44_100 < samplesPerSecond))
abort string("Unsupported Samples Per Second")
clocksPerSample := (clkfreq / samplesPerSecond)
byteRate := fat.readLong
blockAlign := fat.readShort
if((blockAlign <> 1) and (blockAlign <> 2) and (blockAlign <> 4))
abort string("Unsupported Block Align")
if(byteRate <> (samplesPerSecond * blockAlign))
abort string("Invalid Byte Rate")
bitsPerSample := fat.readShort
if((bitsPerSample <> 8) and (bitsPerSample <> 16))
abort string("Unsupported Bits Per Sample")
fat.fileSeek(chunkSkip + fat.fileTell)
repeat while(fat.readLong <> $61_74_61_64)
searchChunkSize := fat.readLong
if((searchChunkSize < 1) or (fat.fileSize == fat.fileTell))
abort string("DATA Chunk Missing")
fat.fileSeek(searchChunkSize + fat.fileTell)
numberOfSamples := (fat.readLong / blockAlign)
callerPointer := callePointer
repeat while(callerPointer == callePointer)
fat.readData(@dataBlock[256 & callerPointer], 512)
samplePosition := numberOfSamples
repeat (((((numberOfSamples * blockAlign) + 511) / 512) - 1) #> 0)
not callerPointer
repeat while(callerPointer == callePointer)
fat.readData(@dataBlock[256 & callerPointer], 512)
PUB DACEngineStart(leftPinNumber, rightPinNumber, initialVolume) '' 9 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Starts up the DAC driver running on a cog.
'' //
'' // Returns true on success or false.
'' //
'' // LeftPinNumber - Pin to use to play the left channel audio. -1 to disable.
'' // RightPinNumber - Pin to use to play the right channel audio. -1 to disable.
'' // InitialVolume - Initial volume for the left and right audio channels. Between 0 and 100. 0 mutes.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
DACEngineStop
if(chipver == 1)
clocksPerSample := clkfreq
rightVolume := leftVolume := (((initialVolume <# 100) #> 0) * constant($1_00_00 / 100))
leftCounterSetup := ((leftPinNumber & $1F) + constant(110 << 26))
rightCounterSetup := ((rightPinNumber & $1F) + constant(110 << 26))
outputMask := (((leftPinNumber <> -1) & (|<leftPinNumber)) | ((rightPinNumber <> -1) & (|<rightPinNumber)))
leftValueAddress := @leftValue
rightValueAddress := @rightValue
samplePositionAddress := @samplePosition
dataBlockAddress := @dataBlock
callePointerAddress := @callePointer
numberOfChannelsAddress := @numberOfChannels
bitsPerSampleAddress := @bitsPerSample
leftVolumeAddress := @leftVolume
rightVolumeAddress := @rightVolume
stopedOrStartedAddress := @stopedOrStarted
beginOrEndAddress := @beginOrEnd
ditherOutLeftAddress := @ditherLeft
ditherOutRightAddress := @ditherRight
cogIdentification := cognew(@initialization, @clocksPerSample)
result or= ++cogIdentification
PUB DACEngineStop '' 3 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Shuts down the DAC driver running on a cog.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if(cogIdentification)
cogstop(-1 + cogIdentification~)
PUB DACDitherEngineStart(leftPinNumber, rightPinNumber, ditherLevel) '' 9 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Starts up the DAC dither driver running on a cog.
'' //
'' // The "DACEngineStart" method starts the regular non-dithering audio driver. If the audio quality is acceptable using it
'' // then the special dithering audio driver is not needed. If the audio quality is not acceptable then use the dither engine
'' // to eliminate both quantization noise from delta-modulation and pico-second jitter noise from on-chip crosstalk between
'' // nearby pins. Normally, these two noise sources generate distracting buzzes, whines, and hash, but dithering removes them
'' // in exchange for lower-level white noise which does not draw attention. The dither level can be adjusted - the further
'' // you get from COG0, the more dither you need. The special dither audio driver will use another cog in addition to the
'' // cog used by the regular non-dithering audio driver. Both audio drivers can output to different audio pins to produce
'' // a dithered audio source and non-dithered audio source. To produce only a dithered audio source disable the left and
'' // right audio channel pins in the regular non-dithering audio driver by passing -1 as the left and right pin numbers. If
'' // only a non-dithered audio source is required do not call the "DACDitherEngineStart" method - this saves a cog.
'' //
'' // Returns true on success or false.
'' //
'' // LeftPinNumber - Pin to use to play the left channel audio. -1 to disable.
'' // RightPinNumber - Pin to use to play the right channel audio. -1 to disable.
'' // DitherLevel - Dither level for the left and right audio channels. 0 = Most Dither ... 31 = Least Dither.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
DACDitherEngineStop
if(chipver == 1)
ditherLeftCounterSetup := ((leftPinNumber & $1F) + constant(110 << 26))
ditherRightCounterSetup := ((rightPinNumber & $1F) + constant(110 << 26))
ditherOutputMask := (((leftPinNumber <> -1) & (|<leftPinNumber)) | ((rightPinNumber <> -1) & (|<rightPinNumber)))
leftDitherShift := rightDitherShift := (ditherLevel & $1F)
ditherLeftAddress := @ditherLeft
ditherRightAddress := @ditherRight
ditherCogIdentification := cognew(@ditherInitialization, 0)
result or= ++ditherCogIdentification
PUB DACDitherEngineStop '' 3 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Shuts down the DAC dither driver running on a cog.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if(ditherCogIdentification)
cogstop(-1 + ditherCogIdentification~)
PUB FATEngineStart(DOPin, CLKPin, DIPin, CSPin, WPPin, CDPin, RTCReserved1, RTCReserved2, RTCReserved3) '' 18 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Starts up the SDC driver running on a cog and checks out a lock for the driver.
'' //
'' // This method should only be called once for any number of included versions of this object.
'' //
'' // This method causes all included versions of this object to need re-mounting when called.
'' //
'' // Returns true on success or false.
'' //
'' // DOPin - The SPI data out pin from the SD card. Between 0 and 31.
'' // CLKPin - The SPI clock pin from the SD card. Between 0 and 31.
'' // DIPin - The SPI data in pin from the SD card. Between 0 and 31.
'' // CSPin - The SPI chip select pin from the SD card. Between 0 and 31.
'' // WPPin - The SPI write protect pin from the SD card holder. Between 0 and 31. -1 if not installed.
'' // CDPin - The SPI write protect pin from the SD card holder. Between 0 and 31. -1 if not installed.
'' // RTCReserved1 - Reserved parameter 1 for RTC compatible driver versions. Pass -1.
'' // RTCReserved2 - Reserved parameter 2 for RTC compatible driver versions. Pass -1.
'' // RTCReserved3 - Reserved parameter 3 for RTC compatible driver versions. Pass -1.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
return fat.FATEngineStart(DOPin, CLKPin, DIPin, CSPin, WPPin, CDPin, RTCReserved1, RTCReserved2, RTCReserved3)
PUB FATEngineStop '' 6 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Shuts down the SDC driver running on a cog and returns the lock used by the driver.
'' //
'' // This method should only be called once for any number of included versions of this object.
'' //
'' // This method causes all included versions of this object to need re-mounting when called.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
fat.FATEngineStop
DAT
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
' DAC Driver
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
org 0
' //////////////////////Initialization/////////////////////////////////////////////////////////////////////////////////////////
initialization mov ctra, leftCounterSetup ' Setup counter modes to duty cycle mode.
mov ctrb, rightCounterSetup '
mov frqa, longAdjust '
mov frqb, longAdjust '
mov dira, outputMask '
mov playerPointer, dataBlockAddress '
rdlong timeCounter, par ' Setup timing.
add timeCounter, cnt '
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
' Player
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
outerLoop rdlong buffer, samplePositionAddress wz ' Enable playback playback given samples.
muxnz playerMode, #4 '
rdword buffer, numberOfChannelsAddress ' Setup number of channels.
cmp buffer, #1 wz '
muxz playerMode, #1 '
rdword buffer, bitsPerSampleAddress ' Setup bits per channel.
cmp buffer, #8 wz '
muxz playerMode, #2 '
mov counter, #128 ' Setup loop counter.
test playerMode, #1 wc '
if_c shl counter, #1 '
test playerMode, #2 wc '
if_c shl counter, #1 '
' //////////////////////Inner Loop/////////////////////////////////////////////////////////////////////////////////////////////
innerLoop rdlong buffer, par ' Wait until next sample output period.
waitcnt timeCounter, buffer '
rdbyte buffer, stopedOrStartedAddress wz ' If stopped loop continously.
if_nz mov frqa, longAdjust '
if_nz mov frqb, longAdjust '
if_nz jmp #innerLoop '
movs signedLongOutput, #leftValueAddress ' Get and output value.
movs multiplicand, #leftVolumeAddress '
movs unsignedLongOutput, #ditherOutLeftAddress '
call #decode '
mov frqa, sampleBuffer '
test playerMode, #1 wc ' Check number of channels.
test playerMode, #2 wz '
if_c_and_nz sub playerPointer, #1 '
if_c_and_z sub playerPointer, #2 '
movs signedLongOutput, #rightValueAddress ' Get and output value.
movs multiplicand, #rightVolumeAddress '
movs unsignedLongOutput, #ditherOutRightAddress '
call #decode '
mov frqb, sampleBuffer '
rdlong buffer, samplePositionAddress ' Decrement position.
cmpsub buffer, #1 wc, wz '
if_c wrlong buffer, samplePositionAddress '
if_z andn playerMode, #4 ' Disable playback given samples.
test playerMode, #4 wc ' Playback begun.
if_c mov buffer, #$FF '
if_c_and_nz wrbyte buffer, beginOrEndAddress '
if_z wrbyte buffer, beginOrEndAddress ' Playback be done.
djnz counter, #innerLoop ' Loop.
' //////////////////////Outer Loop/////////////////////////////////////////////////////////////////////////////////////////////
rdword buffer, callePointerAddress wz ' Switch data block pointer.
sumz buffer, #1 '
wrword buffer, callePointerAddress '
if_nz mov playerPointer, dataBlockAddress '
jmp #outerLoop ' Loop.
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
' Decode Value
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
decode test playerMode, #2 wc ' Read data depending on size.
if_c rdbyte multiplyBuffer, playerPointer ' Read a byte or a word.
if_c add playerPointer, #1 '
if_nc rdword multiplyBuffer, playerPointer '
if_nc add playerPointer, #2 '
if_c sub multiplyBuffer, #128 ' Sign extend.
if_c shl multiplyBuffer, #24 '
if_nc shl multiplyBuffer, #16 '
sar multiplyBuffer, #16 '
test playerMode, #4 wz ' Zero playback given samples.
if_z mov multiplyBuffer, #0 '
signedLongOutput wrlong multiplyBuffer, 0 ' Update main memory.
multiplicand rdword multiplyCounter, 0 ' Setup inputs.
mov sampleBuffer, #0 '
abs multiplyBuffer, multiplyBuffer wc ' Backup sign.
rcr sampleBuffer, #1 wz, nr '
multiplyLoop shr multiplyCounter, #1 wc ' Preform multiplication.
if_c add sampleBuffer, multiplyBuffer '
shl multiplyBuffer, #1 '
tjnz multiplyCounter, #multiplyLoop '
negnz sampleBuffer, sampleBuffer ' Restore sign and center value.
unsignedLongOutput wrlong sampleBuffer, 0 '
add sampleBuffer, longAdjust '
decode_ret ret ' Return.
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
' Data
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
longAdjust long $80_00_00_00 ' Centers output.
' //////////////////////Configuration Settings/////////////////////////////////////////////////////////////////////////////////
leftCounterSetup long 0
rightCounterSetup long 0
outputMask long 0
' //////////////////////Addresses//////////////////////////////////////////////////////////////////////////////////////////////
leftValueAddress long 0
rightValueAddress long 0
samplePositionAddress long 0
dataBlockAddress long 0
callePointerAddress long 0
numberOfChannelsAddress long 0
bitsPerSampleAddress long 0
leftVolumeAddress long 0
rightVolumeAddress long 0
stopedOrStartedAddress long 0
beginOrEndAddress long 0
ditherOutLeftAddress long 0
ditherOutRightAddress long 0
' //////////////////////Run Time Variables/////////////////////////////////////////////////////////////////////////////////////
buffer res 1
counter res 1
playerPointer res 1
playerMode res 1
sampleBuffer res 1
timeCounter res 1
multiplyBuffer res 1
multiplyCounter res 1
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
fit 496
DAT
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
' DAC Dither Driver
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
org 0
' //////////////////////Initialization/////////////////////////////////////////////////////////////////////////////////////////
ditherInitialization mov ctra, ditherLeftCounterSetup ' Setup counter modes to duty cycle mode.
mov ctrb, ditherRightCounterSetup '
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
' Dither
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
ditherLoop test leftLFSR, leftTaps wc ' Iterate left dither source
rcl leftLFSR, #1 '
test rightLFSR, rightTaps wc ' Iterate right dither source
rcl rightLFSR, #1 '
rdlong ditherLBuffer, ditherLeftAddress ' Read left source and shift dither.
mov ditherLCounter, leftLFSR '
sar ditherLCounter, leftDitherShift '
rdlong ditherRBuffer, ditherRightAddress ' Read right source and shift dither.
mov ditherRCounter, rightLFSR '
sar ditherRCounter, rightDitherShift '
add ditherLBuffer, ditherLCounter ' Apply dither.
add ditherLBuffer, ditherAdjust '
mov frqa, ditherLBuffer ' Output.
add ditherRBuffer, ditherRCounter ' Apply dither.
add ditherRBuffer, ditherAdjust '
mov frqb, ditherRBuffer ' Output.
mov dira, ditherOutputMask ' Repeat.
jmp #ditherLoop '
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
' Data
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
ditherAdjust long $80_00_00_00 ' Prevents poping.
leftTaps long $A4_00_00_80 ' Left LFSR taps.
rightTaps long $80_A0_10_00 ' Right LFSR taps.
leftLFSR long 1 ' Initial value.
rightLFSR long 1 ' Initial value.
' //////////////////////Configuration Settings/////////////////////////////////////////////////////////////////////////////////
ditherLeftCounterSetup long 0
ditherRightCounterSetup long 0
ditherOutputMask long 0
leftDitherShift long 0
rightDitherShift long 0
' //////////////////////Addresses//////////////////////////////////////////////////////////////////////////////////////////////
ditherLeftAddress long 0
ditherRightAddress long 0
' //////////////////////Run Time Variables/////////////////////////////////////////////////////////////////////////////////////
ditherLBuffer res 1
ditherLCounter res 1
ditherRBuffer res 1
ditherRCounter res 1
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
fit 496
{{
and place this were ever you decided to play chimes -
dac.playWAVFile(string("chimes.wav"))