220 lines
5.2 KiB
C++
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;
|
|
}
|
|
|
|
|