Arduboy-homemade-package/board-package-source/libraries/ArdVoice/src/ArdVoice.cpp

220 lines
5.2 KiB
C++

/*
Copyright (C) 2016 Ignacio Vina (@igvina)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// ArdVoice: version 0.1
#include "ArdVoice.h"
//#define SAMPLES 180
// 7812,5 ticks/s vs 8000 Hz -> 175,78
#define SAMPLES 176
//Could be size 10 but 16 is faster for % op
#define BUFFER_SIZE 16
uint8_t soundBuffer[BUFFER_SIZE];
const uint8_t *voiceName;
//MAX coeffs 10
uint8_t numberOfCoeffs;
int16_t coeffs[10];
uint8_t samplesMod;
int16_t gain;
uint8_t pitchPeriod;
uint8_t sample_count = 1;
uint8_t sample1 = 0xFF;
int16_t biasOffset;
uint16_t beat;
uint16_t beat_lenght;
ArdVoice::ArdVoice(){};
void ArdVoice::playVoice(const uint8_t *audio){
playVoice(audio, 0, 0, 1.0);
}
void ArdVoice::playVoice(const uint8_t *audio, uint16_t startTime, uint16_t endTime, float speed){
if (!isSoundInit){
isSoundInit = true;
/*
The Arduboy2 library, or equivalent, will control the mode of the
speaker pins itself, in order to handle muting.
If the ArdVoice library is used where another library or sketch doesn't
control the speaker pins, uncomment the following two lines.
*/
// pinMode(PIN_SPEAKER_1, OUTPUT);
// pinMode(PIN_SPEAKER_2, OUTPUT);
TCCR4A = 0b01000010; // Fast-PWM 8-bit
TCCR4B = 0b00000001; // 62500Hz
// It is faster, but generates an annoying noise/buzz
//TCCR4B = 0b00000010; // 62500Hz / 4
OCR4C = 0xFF; // Resolution to 8-bit (TOP=0xFF)
OCR4A = 127;
#ifdef AB_ALTERNATE_WIRING
TCCR4C = 0b01000101;
OCR4D = 127;
#endif
}
voiceName = audio;
byte readedByte = pgm_read_byte(&voiceName[1]);
uint16_t chunks = ((readedByte & 0x0F) << 8) | (pgm_read_byte(&voiceName[0]) & 0xFF);
numberOfCoeffs = (readedByte >> 4) & 0x0F;
beat = (startTime * 2/*8*/) / /*180*/45;
sample1=0;
sample_count=1;
samplesMod = (uint8_t)(SAMPLES * speed);
beat_lenght = endTime == 0 ? chunks : (endTime * 2/*8*/) / /*180*/45;
//Init timer
TIMSK4 = 0b00000100;
}
//Timer
ISR(TIMER4_OVF_vect){
sample_count--;
if(sample1 != 0xFF){
if(sample_count == 0){
// Should be sample_count = 8, but it's slow to process
sample_count = 4;
// Read LPC coeffs on first sample
if (sample1 == 0){
// Reset buffer
memset(soundBuffer, 127, BUFFER_SIZE);
// Get array offset
uint16_t offset = 2 + beat * (2 + numberOfCoeffs);
beat++;
// Check if voice ended
if(beat > beat_lenght){
// Stop timer and return
OCR4A = 127;
#ifdef AB_ALTERNATE_WIRING
OCR4D = 127;
#endif
TIMSK4 = 0;
sample1 = 0xFF;
return;
}
byte readedByte = pgm_read_byte(&voiceName[offset]);
pitchPeriod = readedByte & 0xb10000000;
if (pitchPeriod == 0){
pitchPeriod = (readedByte & 0xFF) + 20;
} else {
pitchPeriod = 0;
}
gain = ((pgm_read_byte(&voiceName[offset+1])& 0xFF));;
biasOffset = 64;
for (uint8_t i = 0; i < numberOfCoeffs ; i++){
readedByte = pgm_read_byte(&voiceName[offset + 2 + i]);
if ((readedByte & 0b10000000) == 0){
coeffs[i] =((readedByte & 0xFF) - 64);
} else{
coeffs[i] = (((64 * 64) / ((readedByte & 0b01111111) - 64 )));
}
//TIP: Faster to precalculate
biasOffset += coeffs[i];
}
biasOffset *= 127;
}
// Get next sample
int16_t temp = pitchPeriod == 0 ?
gain * ((fastRand8())-127):
(sample1 % pitchPeriod) == 0 ? gain * 127: 0;
uint8_t sampleOffset = sample1 % BUFFER_SIZE;
for (uint8_t i = 0; i < numberOfCoeffs; i++) {
temp -= coeffs[i] * ((soundBuffer[(sampleOffset- i + BUFFER_SIZE - 1)%BUFFER_SIZE] & 0xFF));
}
temp = (temp + biasOffset) / 64;
// Fix out of range values
temp = temp < 0 ? 0 : temp > 255 ? 255 : temp;
OCR4A = soundBuffer[sampleOffset] = temp & 0xFF;
#ifdef AB_ALTERNATE_WIRING
OCR4D = soundBuffer[sampleOffset];
#endif
sample1++;
// Jump to next beat
if (sample1 == samplesMod) sample1 = 0;
}
}
}
void ArdVoice::stopVoice(){
OCR4A = 127;
#ifdef AB_ALTERNATE_WIRING
OCR4D = 127;
#endif
TIMSK4 = 0;
sample1 = 0xFF;
}
boolean ArdVoice::isVoicePlaying(){
return sample1 != 0xFF;
}
uint8_t fastRand8() {
static uint8_t state[STATE_BYTES] = { 0x87, 0xdd, 0xdc, 0x10, 0x35, 0xbc, 0x5c };
static uint16_t c = 0x42;
static unsigned int i = 0;
uint16_t t;
uint8_t x;
x = state[i];
t = (uint16_t)x * MULT_LO + c;
c = t >> 8;
#if MULT_HI
c += x;
#endif
x = t & 255;
state[i] = x;
if (++i >= sizeof(state))
i = 0;
return x;
}