From 52d4d5721b246a676ed42bc762ec626ecdfe8398 Mon Sep 17 00:00:00 2001 From: bsongis Date: Sun, 29 Jan 2012 18:43:21 +0000 Subject: [PATCH] AUDIO now ok on v4 Code refactoring using a circular buffer for the queue: 212bytes flash saved, 7bytes ram saved, no more copy loops --- src/audio.cpp | 391 ++++++++++------------------------------------ src/audio.h | 62 +++----- src/beeper.h | 20 +-- src/s9xsplash.xcf | Bin 13358 -> 0 bytes 4 files changed, 108 insertions(+), 365 deletions(-) delete mode 100644 src/s9xsplash.xcf diff --git a/src/audio.cpp b/src/audio.cpp index c627da68e..0ff3146ae 100644 --- a/src/audio.cpp +++ b/src/audio.cpp @@ -22,20 +22,13 @@ audioQueue::audioQueue() void audioQueue::aqinit() { //make sure haptic off by default - HAPTIC_OFF; - //initialize all arrays - flushqueue(0); - - //set key vars to 0 to ensure no garbage - toneFreq = 0; - toneFreqEnd = 0; - toneRepeat = 0; toneTimeLeft = 0; - queueState = 0; - toneRepeatCnt = 0; - inToneRepeat = 0; + tonePause = 0; + + t_queueRidx = 0; + t_queueRidx = 0; #ifdef HAPTIC hapticTick = 0; @@ -45,196 +38,70 @@ void audioQueue::aqinit() frskySample = 0; #endif - heartbeatTimer = 0; - flushTemp(); } bool audioQueue::busy() { - if (toneTimeLeft > 0) { - return true; - } - else { - return false; - } + return (toneTimeLeft > 0); } +/* TODO to be added when needed bool audioQueue::freeslots(uint8_t slots) { - //find out if enough queue space exists to add the requested routine! - for (uint8_t i = AUDIO_QUEUE_LENGTH; i--;) { - slots--; - if (slots == 0) { - break; - } //only loop for the number of items indicated in slots - if (queueToneStart[i] != 0) { - return false; - break; - } - } - return true; -} - -void audioQueue::commit(uint8_t toneInterupt) -{ - - if (toneInterupt == 0) { - //queued events - if (freeslots(3)) { //do not insert into the queue if less than 3 spare slots! this stops sound collitions from occuring - for (uint8_t i = 0; i < AUDIO_QUEUE_LENGTH; i++) { - if (queueToneStart[i] == 0) { //add to the first entry that has a start tone of zero (no sound) - queueToneStart[i] = t_queueToneStart; - queueToneEnd[i] = t_queueToneEnd; - queueToneLength[i] = t_queueToneLength; - queueTonePause[i] = t_queueTonePause; - queueToneRepeat[i] = t_queueToneRepeat; -#ifdef HAPTIC - queueToneHaptic[i] = t_queueToneHaptic; -#endif - flushTemp(); - break; - } - } - } - } - else { - //interrupt events - queueToneStart[0] = t_queueToneStart; - queueToneEnd[0] = t_queueToneEnd; - queueToneLength[0] = t_queueToneLength; - queueTonePause[0] = t_queueTonePause; - queueToneRepeat[0] = t_queueToneRepeat; -#ifdef HAPTIC - queueToneHaptic[0] = t_queueToneHaptic; -#endif - - flushqueue(1); //purge queue on interrupt events to stop broken audio - flushTemp(); - } - -} - -void audioQueue::flushqueue(uint8_t startpos) -{ - for (; startpos < AUDIO_QUEUE_LENGTH; startpos++) { - queueToneStart[startpos] = 0; - queueToneEnd[startpos] = 0; - queueToneLength[startpos] = 0; - queueTonePause[startpos] = 0; - queueToneRepeat[startpos] = 0; -#ifdef HAPTIC - queueToneHaptic[startpos] = 0; -#endif - } -} - -//set all temporary buffers to default -void audioQueue::flushTemp() -{ - t_queueToneStart = 0; - t_queueToneEnd = 0; - t_queueToneLength = 0; - t_queueTonePause = 0; - t_queueToneRepeat = 0; -#ifdef HAPTIC - t_queueToneHaptic = 0; -#endif - rateOfChange = 0; -#ifdef HAPTIC - toneHaptic = 0; -#endif -} - -void audioQueue::restack() -{ - for (uint8_t i = 0; i < AUDIO_QUEUE_LENGTH - 1; i++) { - queueToneStart[i] = queueToneStart[i + 1]; - queueToneEnd[i] = queueToneEnd[i + 1]; - queueToneLength[i] = queueToneLength[i + 1]; - queueTonePause[i] = queueTonePause[i + 1]; - queueToneRepeat[i] = queueToneRepeat[i + 1]; -#ifdef HAPTIC - queueToneHaptic[i] = queueToneHaptic[i + 1]; -#endif - } - flushqueue(AUDIO_QUEUE_LENGTH - 1); //set the last entry to 0 as nothing in stack to add too! + return AUDIO_QUEUE_LENGTH - ((t_queueWidx + AUDIO_QUEUE_LENGTH - t_queueRidx) % AUDIO_QUEUE_LENGTH) >= slots; } +*/ //heartbeat is responsibile for issueing the audio tones and general square waves // it is essentially the life of the class. void audioQueue::heartbeat() { +#if defined(PCBSTD) + if (toneTimeLeft > 0) { + //square wave generator use for speaker mod + //simply generates a square wave for toneFreq for + //as long as the toneTimeLeft is more than 0 + static uint8_t toneCounter; + toneCounter += toneFreq; + if ((toneCounter & 0x80) == 0x80) + PORTE |= (1 << OUT_E_BUZZER); // speaker output 'high' + else + PORTE &= ~(1 << OUT_E_BUZZER); // speaker output 'low' + } - uint8_t z; //direction calulations - uint8_t hTimer; //heartbeat timer + static uint8_t cnt10ms = 77; // execute 10ms code once every 78 ISRs + if (cnt10ms-- == 0) // every 10ms ... + cnt10ms = 77; + else + return; +#endif - if (queueState == 1) { - - if (g_eeGeneral.beeperVal > 0) { - //never do sounds if we are set to go quiet -#if 0 - switch (g_eeGeneral.speakerMode) { - - case 0: - //stock beeper. simply turn port on for x time! - if (toneTimeLeft > 0) { + if (toneTimeLeft > 0) { #if defined(PCBV4) - TCCR0A |= (0b01< 0) { - toneCounter += toneFreq; - if ((toneCounter & 0x80) == 0x80) { -#if defined(PCBV4) - TCCR0A |= (0b01< 0 -#ifdef HAPTIC - || queueToneHaptic[0] == 1 -#endif - ) && toneTimeLeft <= 0 - && queueState == 0) { - if (queueToneEnd[0] > 0 && queueToneEnd[0] != queueToneStart[0]) { - if (queueToneStart[0] > queueToneEnd[0]) { //tone going down - z = queueToneStart[0] - queueToneEnd[0]; - rateOfChange = -2; - } - else { //tone going up - z = queueToneEnd[0] - queueToneStart[0]; - rateOfChange = 2; - } - if (queueToneStart[0] > 0) { - toneFreq = queueToneStart[0] + g_eeGeneral.speakerPitch + BEEP_OFFSET; // add pitch compensator - } - else { - toneFreq = queueToneStart[0]; // done so hapticOnly option can work - } - toneFreqEnd = queueToneEnd[0] + g_eeGeneral.speakerPitch + BEEP_OFFSET; - toneTimeLeft = z; - tonePause = queueTonePause[0]; - toneRepeat = queueToneRepeat[0]; -#ifdef HAPTIC - toneHaptic = queueToneHaptic[0]; -#endif - } - else { - //simple tone handler - if (queueToneStart[0] > 0) { - toneFreq = (queueToneStart[0] + g_eeGeneral.speakerPitch) + BEEP_OFFSET; // add pitch compensator - } - else { - toneFreq = queueToneStart[0]; - } - rateOfChange = 0; - toneFreqEnd = 0; - toneTimeLeft = queueToneLength[0]; - tonePause = queueTonePause[0]; - toneRepeat = queueToneRepeat[0]; -#ifdef HAPTIC - toneHaptic = queueToneHaptic[0]; -#endif - } - queueState = 1; - if (toneRepeat != 0 && inToneRepeat == 0) { - inToneRepeat = 1; - toneRepeatCnt = toneRepeat; - } - - if (inToneRepeat == 1) { - toneRepeatCnt--; - if (toneRepeatCnt <= 0) { - inToneRepeat = 0; - restack(); - } - } - else { - restack(); - } - - } - - hTimer = AUDIO_QUEUE_HEARTBEAT_NORM; // default +inline uint8_t audioQueue::getToneLength(uint8_t tLen) +{ + uint8_t result = tLen; // default if (g_eeGeneral.beeperVal == 2) { - //xshort - hTimer = AUDIO_QUEUE_HEARTBEAT_XSHORT; + result /= 2; } else if (g_eeGeneral.beeperVal == 3) { - //short - hTimer = AUDIO_QUEUE_HEARTBEAT_SHORT; + result = (result * 3) / 2; } else if (g_eeGeneral.beeperVal == 5) { //long - hTimer = AUDIO_QUEUE_HEARTBEAT_LONG; + result *= 2; } else if (g_eeGeneral.beeperVal == 6) { //xlong - hTimer = AUDIO_QUEUE_HEARTBEAT_XLONG; + result *= 3; } + return result; +} - heartbeatTimer++; - if (heartbeatTimer >= hTimer) { +void audioQueue::playNow(uint8_t tFreq, uint8_t tLen, uint8_t tPause, + uint8_t tRepeat, uint8_t tHaptic, int8_t tFreqIncr) +{ + if (g_eeGeneral.beeperVal) { + toneFreq = tFreq + g_eeGeneral.speakerPitch + BEEP_OFFSET; // add pitch compensator + toneTimeLeft = getToneLength(tLen); + tonePause = tPause; + #ifdef HAPTIC + t_queueToneHaptic = tHaptic; + #endif + toneFreqIncr = tFreqIncr; + t_queueWidx = t_queueRidx; - heartbeatTimer = 0; - - if (queueState == 1) { - if (toneTimeLeft > 0) { - toneFreq += rateOfChange; // -2, 0 or 2 - toneTimeLeft--; //time gets counted down - } - if (toneTimeLeft <= 0) { - if (tonePause-- <= 0) { - queueState = 0; - } - } + if (tRepeat) { + playASAP(tFreq, tLen, tPause, tRepeat-1, tHaptic, tFreqIncr); } - } - } -void audioQueue::playNow(uint8_t tStart, uint8_t tLen, uint8_t tPause, - uint8_t tRepeat, uint8_t tHaptic, uint8_t tEnd) +void audioQueue::playASAP(uint8_t tFreq, uint8_t tLen, uint8_t tPause, + uint8_t tRepeat, uint8_t tHaptic, int8_t tFreqIncr) { - t_queueToneStart = tStart; - t_queueToneLength = tLen; - t_queueTonePause = tPause; + if (g_eeGeneral.beeperVal) { + queueToneFreq[t_queueWidx] = tFreq + g_eeGeneral.speakerPitch + BEEP_OFFSET; // add pitch compensator + queueToneLength[t_queueWidx] = getToneLength(tLen); + queueTonePause[t_queueWidx] = tPause; #ifdef HAPTIC - t_queueToneHaptic = tHaptic; + queueToneHaptic[t_queueWidx] = tHaptic; #endif - t_queueToneRepeat = tRepeat; - t_queueToneEnd = tEnd; - commit(1); -} + queueToneRepeat[t_queueWidx] = tRepeat; + queueToneFreqIncr[t_queueWidx] = tFreqIncr; -void audioQueue::playASAP(uint8_t tStart, uint8_t tLen, uint8_t tPause, - uint8_t tRepeat, uint8_t tHaptic, uint8_t tEnd) -{ - t_queueToneStart = tStart; - t_queueToneLength = tLen; - t_queueTonePause = tPause; -#ifdef HAPTIC - t_queueToneHaptic = tHaptic; -#endif - t_queueToneRepeat = tRepeat; - t_queueToneEnd = tEnd; - commit(0); + t_queueWidx = (t_queueWidx + 1) % AUDIO_QUEUE_LENGTH; + } } #ifdef FRSKY //this is done so the menu selections only plays tone once! -void audioQueue::frskyeventSample(uint8_t e) { +void audioQueue::frskyeventSample(uint8_t e) +{ if(frskySample != e) { aqinit(); //flush the queue frskyevent(e); @@ -408,7 +188,8 @@ void audioQueue::frskyeventSample(uint8_t e) { } } -void audioQueue::frskyevent(uint8_t e) { +void audioQueue::frskyevent(uint8_t e) +{ // example playASAP(tStart,tLen,tPause,tRepeat,tHaptic,tEnd); switch(e) { case AU_FRSKY_WARN1: @@ -615,15 +396,13 @@ void audioQueue::event(uint8_t e, uint8_t f) { //low battery in tx //case 19: case AU_TX_BATTERY_LOW: - playASAP(60, 4, 3, 2, 1, 70); - playASAP(80, 4, 3, 2, 1, 70); + playASAP(60, 4, 3, 2, 1, 2); + playASAP(80, 4, 3, 2, 1, -2); break; default: break; - } - } void audioDefevent(uint8_t e) { diff --git a/src/audio.h b/src/audio.h index 4712cc96a..74bc85fc6 100644 --- a/src/audio.h +++ b/src/audio.h @@ -22,11 +22,11 @@ #define BEEP_KEY_UP_FREQ (BEEP_DEFAULT_FREQ+5) #define BEEP_KEY_DOWN_FREQ (BEEP_DEFAULT_FREQ-5) -#define AUDIO_QUEUE_HEARTBEAT_XSHORT (30) //speaker timing [Norm] -#define AUDIO_QUEUE_HEARTBEAT_SHORT (50) //speaker timing [Norm] -#define AUDIO_QUEUE_HEARTBEAT_NORM (77) //speaker timing [Norm] -#define AUDIO_QUEUE_HEARTBEAT_LONG (130) //speaker timing [Long] -#define AUDIO_QUEUE_HEARTBEAT_XLONG (200) //speaker timing [xLong] +// #define AUDIO_QUEUE_HEARTBEAT_XSHORT (30) //speaker timing [Norm] +// #define AUDIO_QUEUE_HEARTBEAT_SHORT (50) //speaker timing [Norm] +// #define AUDIO_QUEUE_HEARTBEAT_NORM (77) //speaker timing [Norm] +// #define AUDIO_QUEUE_HEARTBEAT_LONG (130) //speaker timing [Long] +// #define AUDIO_QUEUE_HEARTBEAT_XLONG (200) //speaker timing [xLong] #ifdef HAPTIC #define HAPTIC_ON PORTG |= (1<<2) #define HAPTIC_OFF PORTG &= ~(1<<2) @@ -85,41 +85,29 @@ #define BEEP_LONG (5) #define BEEP_XLONG (6) -struct audioQueue { - - //queue temporaries - uint8_t t_queueToneStart; - uint8_t t_queueToneEnd; - uint8_t t_queueToneLength; - uint8_t t_queueTonePause; - uint8_t t_queueToneRepeat; -#ifdef HAPTIC - uint8_t t_queueToneHaptic; -#endif +struct audioQueue +{ + uint8_t t_queueRidx; + uint8_t t_queueWidx; //queue general vars uint8_t toneFreq; - uint8_t toneFreqEnd; + int8_t toneFreqIncr; uint8_t toneTimeLeft; - int8_t rateOfChange; uint8_t tonePause; - uint8_t queueState; - uint8_t toneRepeat; - uint8_t toneRepeatCnt; - uint8_t inToneRepeat; + #ifdef HAPTIC uint8_t toneHaptic; uint8_t hapticTick; #endif - uint8_t heartbeatTimer; #ifdef FRSKY uint8_t frskySample; #endif - //queue arrays - uint8_t queueToneStart[AUDIO_QUEUE_LENGTH]; - uint8_t queueToneEnd[AUDIO_QUEUE_LENGTH]; + // queue arrays + uint8_t queueToneFreq[AUDIO_QUEUE_LENGTH]; + int8_t queueToneFreqIncr[AUDIO_QUEUE_LENGTH]; uint8_t queueToneLength[AUDIO_QUEUE_LENGTH]; uint8_t queueTonePause[AUDIO_QUEUE_LENGTH]; uint8_t queueToneRepeat[AUDIO_QUEUE_LENGTH]; @@ -127,17 +115,16 @@ struct audioQueue { uint8_t queueToneHaptic[AUDIO_QUEUE_LENGTH]; #endif - //beep length table - // uint8_t beepLenTable[10]; - audioQueue(); void aqinit(); // To stop constructor being compiled twice + inline uint8_t getToneLength(uint8_t tLen); + //only difference between these two functions is that one does the //interupt queue (Now) and the other queues for playing ASAP. - void playNow(uint8_t tStart,uint8_t tLen,uint8_t tPause,uint8_t tRepeat=0,uint8_t tHaptic=0,uint8_t tEnd=0); - void playASAP(uint8_t tStart,uint8_t tLen,uint8_t tPause,uint8_t tRepeat=0,uint8_t tHaptic=0,uint8_t tEnd=0); + void playNow(uint8_t tFreq,uint8_t tLen,uint8_t tPause,uint8_t tRepeat=0,uint8_t tHaptic=0,int8_t tFreqIncr=0); + void playASAP(uint8_t tFreq,uint8_t tLen,uint8_t tPause,uint8_t tRepeat=0,uint8_t tHaptic=0,int8_t tFreqIncr=0); bool busy(); @@ -148,26 +135,19 @@ struct audioQueue { void frskyeventSample(uint8_t e); #endif - void commit(uint8_t toneInterupt); - - //set all temporary buffers to default - void flushTemp(); - void flushqueue(uint8_t startpos); - - void restack(); //heartbeat is responsibile for issueing the audio tones and general square waves // it is essentially the life of the class. void heartbeat(); - bool freeslots(uint8_t slots); + + // TODO bool freeslots(uint8_t slots); }; //wrapper function - dirty but results in a space saving!!! extern audioQueue audio; + //void audioevent(uint8_t e,uint8_t f=BEEP_DEFAULT_FREQ); void audioDefevent(uint8_t e); -void audioError(); - #define AUDIO_KEYPAD_UP() audioDefevent(AU_KEYPAD_UP) #define AUDIO_KEYPAD_DOWN() audioDefevent(AU_KEYPAD_DOWN) #define AUDIO_MENUS() audioDefevent(AU_MENUS) diff --git a/src/beeper.h b/src/beeper.h index 3e7a316a8..88d1180a4 100644 --- a/src/beeper.h +++ b/src/beeper.h @@ -79,30 +79,14 @@ inline void AUDIO_HEARTBEAT() if (beepOn) { static bool warbleC; warbleC = warble && !warbleC; - if (warbleC) { -#if defined(PCBV4) - TCCR0A &= ~( 1 << COM0A0); -#else + if (warbleC) PORTE &= ~(1 << OUT_E_BUZZER); // speaker output 'low' -#endif - } - else { -#if defined(PCBV4) - TCCR0A |= (1 << COM0A0); // tone on -#else + else PORTE |= (1 << OUT_E_BUZZER); // speaker output 'high' -#endif - } } else { -#if defined(PCBV4) - TCCR0A &= ~(1 << COM0A0); -#else PORTE &= ~(1 << OUT_E_BUZZER); // speaker output 'low' -#endif } } - - #endif diff --git a/src/s9xsplash.xcf b/src/s9xsplash.xcf deleted file mode 100644 index 580636ef9cd7ac163a1cdfba00f7f7b68eb71c11..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13358 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 m8