// Define if you wish to debug memory usage. Only works on T4.x
//#define DEBUG_MEMORY
#include <SPI.h>
#include <array>
#include <Wire.h>
#include <FastTouch.h>
#include <Streaming.h>
/*
https://forum.pjrc.com/threads/57423-Teensy-4-0-I2S-without-Audio-Shield-and-flash-memory-question?p=214254&viewfull=1#post214254
Modified to eliminate the audio shield, and use Max98357A mono I2S chip.
https://smile.amazon.com/gp/product/B07PS653CD/ref=ppx_yo_dt_b_asin_title_o00_s00?ie=UTF8&psc=1
Pins: Teensy 4.0 Teensy 3.x
LRCLK: Pin 20/A6 Pin 23/A9
BCLK: Pin 21/A7 Pin 9
DIN: Pin 7 Pin 22/A8
Gain: see below N/C
Shutdown: N/C N/C
Ground: Ground Ground
VIN: VIN = 5V
Gain setting:
15dB if a 100K resistor is connected between GAIN and GND
12dB if GAIN is connected directly to GND
9dB if GAIN is not connected to anything (this is the default)
6dB if GAIN is conneted directly to Vin
3dB if a 100K resistor is connected between GAIN and Vin. */
#include <Audio.h>
#include <play_sd_mp3.h> //mp3 decoder
#include <play_sd_aac.h> // AAC decoder
#include <SD.h>
#include "config.h"
#include "util/logging.h"
#include "sensors/LightSensor.h"
#include "sensors/PersonSensor.h"
const int chipSelect = BUILTIN_SDCARD;
File root;
File song;
AudioPlaySdMp3 playMp31;
AudioPlaySdAac playAac1;
// GUItool: begin automatically generated code
AudioPlaySdWav playSdWav1; //xy=72.16667175292969,98.00000762939453
AudioMixer4 mixer2; //xy=230.1666717529297,199
AudioMixer4 mixer1; //xy=234.1666717529297,76.00000762939453
//AudioAmplifier amp1; //xy=416.16668701171875,408
AudioMixer4 mixer3; //xy=424.1666717529297,132.1666717529297
AudioAnalyzeFFT256 fft256_1; //xy=577.1666870117188,267
AudioAnalyzePeak peak1; //xy=579.1666870117188,201
AudioSynthWaveformDc dc1; //xy=439.1666717529297,34.16667175292969
AudioEffectMultiply multiply1; //xy=599.1666717529297,74.16667175292969
AudioOutputI2S i2s1; //xy=779.1666870117188,126
AudioConnection patchCord11a(playMp31, 0, mixer1, 1);
AudioConnection patchCord13m(playMp31, 1, mixer2, 1);
AudioConnection patchCord14m(playAac1, 0, mixer1, 2);
AudioConnection patchCord16(playAac1, 1, mixer2, 2);
AudioConnection patchCord3(mixer2, 0, mixer3, 1);
AudioConnection patchCord4(mixer1, 0, mixer3, 0);
AudioConnection patchCord5(mixer3, peak1);
AudioConnection patchCord6(mixer3, fft256_1);
AudioConnection patchCord7(mixer3, 0, multiply1, 1);
AudioConnection patchCord8(dc1, 0, multiply1, 0);
AudioConnection patchCord9(multiply1, 0, i2s1, 0);
//AudioControlSGTL5000 sgtl5000_1; //xy=573.1666870117188,400
// GUItool: end automatically generated code
char rootFiles[][33] = { "A Banda.mp3", "BuffaloGals.m4a", "BugleCallRag.m4a", "Cast Your Fate To the Wind.mp3", "Celestial Soda Pop.mp3",
"DuelingBanjos.mp3", "Hallelujah.mp3", "Livin' On A Prayer.mp3", "Oops.mp3", "The Thought Stayed Free.mp3", "The Train And The River.mp3",
"We Don't Talk About Bruno.mp3"};
uint64_t songStart;
boolean shortSwitch = false;
// The index of the currently selected eye definitions
static uint32_t defIndex{0};
LightSensor lightSensor(LIGHT_PIN);
PersonSensor personSensor;
bool hasBlinkButton() {
return BLINK_PIN >= 0;
}
bool hasLightSensor() {
return LIGHT_PIN >= 0;
}
bool hasJoystick() {
return JOYSTICK_X_PIN >= 0 && JOYSTICK_Y_PIN >= 0;
}
bool hasPersonSensor() {
return PERSON_SENSOR_PRESENT;
}
void stopPlaying() {
song.close();
if (playSdWav1.isPlaying()) { playSdWav1.stop(); }
if (playMp31.isPlaying()) { playMp31.stop(); }
if (playAac1.isPlaying()) { playAac1.stop(); }
}
boolean playing () {
//Serial<<"shortSwitch: "<<shortSwitch<<" duration: "<<millis()-songStart<<endl;
if ( shortSwitch & ((millis() - songStart ) >20000) ) {stopPlaying();/*Serial<<"timed out"<<endl;*/}
return (playSdWav1.isPlaying() | playMp31.isPlaying() | playAac1.isPlaying() );
}
void adjustVolume(float vol) {
dc1.amplitude(vol); //with the max98357, mixer.gain does not adjust volume (don't know why)
}
void playFile(String curfile) {
char charry[120];
String fullFile = root.name();
if (fullFile.length() > 2) fullFile.append('/'); //avoid "//" at root
fullFile.append(curfile);
fullFile.toCharArray(charry,119);
Serial<<" fullFile: "<<fullFile;
curfile.toUpperCase();
songStart = millis(); //play just 20 seconds of song -- see playing() above
//look for MP3 or AAC files
if (curfile.lastIndexOf(".MP3") >0) playMp31.play(charry);
if (curfile.lastIndexOf(".WAV") >0) playSdWav1.play(charry);
if (curfile.lastIndexOf(".AAC") >0) playAac1.play(charry);
if (curfile.lastIndexOf(".MP4") >0) playAac1.play(charry);
if (curfile.lastIndexOf(".M4A") >0) playAac1.play(charry);
delay(100);
if (! playing()) {
Serial<<" Song did not start?? size="<<song.size();
}
Serial<<endl;
//Serial<<"Playing file: "<<charry;
//lightsOption = random(6); //one of lightsAction,rainbowSpin1, rainbowSpin2, comet, twinkle, rainbow3
//lightsOption = 5; //when testing just set one
//Serial<<"lightsOption: "<<lightsOption<<endl;
}
/// INITIALIZATION -- runs once at startup ----------------------------------
void setup() {
Serial.begin(115200);
while (!Serial && millis() < 2000);
delay(200);
DumpMemoryInfo();
Serial.println("Init");
Serial.flush();
randomSeed(analogRead(A3)); // Seed random() from floating analog input
AudioMemory (160);
adjustVolume(0.3f);
SD.begin(chipSelect);
if (hasBlinkButton()) {
pinMode(BLINK_PIN, INPUT_PULLUP);
}
if (hasJoystick()) {
pinMode(JOYSTICK_X_PIN, INPUT);
pinMode(JOYSTICK_Y_PIN, INPUT);
}
if (hasPersonSensor()) {
Wire.begin();
personSensor.enableID(false);
personSensor.enableLED(false);
personSensor.setMode(PersonSensor::Mode::Continuous);
}
initEyes(!hasJoystick(), !hasBlinkButton(), !hasLightSensor());
}
void nextEye() {
defIndex = (defIndex + 1) % eyeDefinitions.size();
eyes->updateDefinitions(eyeDefinitions.at(defIndex));
}
/// MAIN LOOP -- runs continuously after setup() ----------------------------
void loop() {
// Switch eyes periodically
static elapsedMillis eyeTime{};
if (fastTouchRead(41)> 23 && eyeTime > EYE_DURATION_MS) { //jrr
nextEye();
eyeTime = 0;
} else {
//Serial<<"FastTouchRead(41): "<<fastTouchRead(41)<<"eyeTime: "<<eyeTime<<endl;
}
if (fastTouchRead(40)>23 && !playing()) {
root=SD.open("/");
playFile(rootFiles[1]); //obviously too simple--see if it works
}else {
//Serial<<"FastTouchRead(40): "<<fastTouchRead(40)<<"playing: "<<playing()<<endl;
}
// Blink on button press
if (hasBlinkButton() && digitalRead(BLINK_PIN) == LOW) {
eyes->blink();
}
// Move eyes with an analog joystick
if (hasJoystick()) {
auto x = analogRead(JOYSTICK_X_PIN);
auto y = analogRead(JOYSTICK_Y_PIN);
eyes->setPosition((x - 512) / 512.0f, (y - 512) / 512.0f);
}
if (hasLightSensor()) {
lightSensor.readDamped([](float value) {
eyes->setPupil(value);
});
}
if (hasPersonSensor() && personSensor.read()) {
// Find the closest face that is facing the camera, if any
int maxSize = 0;
person_sensor_face_t maxFace{};
for (int i = 0; i < personSensor.numFacesFound(); i++) {
const person_sensor_face_t face = personSensor.faceDetails(i);
if (face.is_facing && face.box_confidence > 150) {
int size = (face.box_right - face.box_left) * (face.box_bottom - face.box_top);
if (size > maxSize) {
maxSize = size;
maxFace = face;
}
}
}
if (maxSize > 0) {
eyes->setAutoMove(false);
float targetX = (static_cast<float>(maxFace.box_left) + static_cast<float>(maxFace.box_right - maxFace.box_left) / 2.0f) / 127.5f - 1.0f;
float targetY = (static_cast<float>(maxFace.box_top) + static_cast<float>(maxFace.box_bottom - maxFace.box_top) / 3.0f) / 127.5f - 1.0f;
eyes->setTargetPosition(targetX, targetY);
} else if (personSensor.timeSinceFaceDetectedMs() > 1'000) {
eyes->setAutoMove(true);
}
}
eyes->renderFrame();
}