diff --git a/bin/data/Avara-Bold.otf b/bin/data/Avara-Bold.otf new file mode 100644 index 0000000..6796b89 Binary files /dev/null and b/bin/data/Avara-Bold.otf differ diff --git a/bin/data/Avara.otf b/bin/data/Avara.otf new file mode 100644 index 0000000..a975c8e Binary files /dev/null and b/bin/data/Avara.otf differ diff --git a/src/main.cpp b/src/main.cpp index f5f4b4a..587a468 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,6 +19,8 @@ int main( ){ shared_ptr mainApp(new ofApp); shared_ptr teleprompterApp(new ofTeleprompter); + mainApp->teleprompter = teleprompterApp; + ofRunApp(mainWindow, mainApp); ofRunApp(secondWindow, teleprompterApp); diff --git a/src/ofApp.cpp b/src/ofApp.cpp index d101ecd..701470d 100644 --- a/src/ofApp.cpp +++ b/src/ofApp.cpp @@ -25,6 +25,9 @@ void ofApp::update(){ videoFrame.setFromPixels(p); } onnx.update(videoFrame); + + teleprompter->updateCVData(onnx.detectedFaces.size()); + ofLog() << ofGetFrameRate(); } diff --git a/src/ofApp.h b/src/ofApp.h index 39098e4..8164946 100644 --- a/src/ofApp.h +++ b/src/ofApp.h @@ -2,6 +2,7 @@ #include "ofMain.h" #include "onxProcess.h" +#include "ofTeleprompter.h" class ofApp : public ofBaseApp{ @@ -20,4 +21,6 @@ class ofApp : public ofBaseApp{ ofImage inputImage; ofImage videoFrame; + + shared_ptr teleprompter; }; diff --git a/src/ofTeleprompter.cpp b/src/ofTeleprompter.cpp index 3b775b8..da60951 100644 --- a/src/ofTeleprompter.cpp +++ b/src/ofTeleprompter.cpp @@ -6,19 +6,44 @@ void ofTeleprompter::setup() { ofSetVerticalSync(false); setupGUI(); loadText(); + + textFont.load("Avara.otf", 32); + detailsFont.load("Avara-Bold.otf", 24); + + // Prepare first line for teleprompter + currentSentence = script[currentLine].sentence; + displayedSentence.clear(); + currentLetterIndex = 0; + lastWordTime = ofGetElapsedTimeMillis(); } void ofTeleprompter::update() { + // set labels + currentSpeaker = script[currentLine].speaker; + currentEmotion = script[currentLine].emotion; + currentLineIndex = ofToString(currentLine); + // Teleprompter logic (letter by letter) + if (currentLetterIndex < currentSentence.size()) { + uint64_t now = ofGetElapsedTimeMillis(); + if (now - lastWordTime > wordDelay) { + displayedSentence += currentSentence[currentLetterIndex]; + currentLetterIndex++; + lastWordTime = now; + } + } } void ofTeleprompter::draw() { gui.draw(); + drawText(); + } void ofTeleprompter::setupGUI() { nextLine.addListener(this, &ofTeleprompter::nextLinePressed); + gui.setDefaultWidth(400); gui.setup(); gui.add(currentLineIndex.setup("Current Line Index", "NULL")); gui.add(currentSpeaker.setup("Current Speaker", "NULL")); @@ -34,9 +59,112 @@ void ofTeleprompter::setupGUI() { void ofTeleprompter::nextLinePressed() { ofLog() << "Next Line!"; + currentLine++; + + // Prepare teleprompter effect for letter-by-letter + currentSentence = script[currentLine].sentence; + displayedSentence.clear(); + currentLetterIndex = 0; + lastWordTime = ofGetElapsedTimeMillis(); } void ofTeleprompter::loadText() { + script.clear(); - + ofFile jsonFile(filePath); + if(jsonFile.exists()) { + ofJson json = ofLoadJson(jsonFile); + int idx = 0; + for (const auto& entry : json) { + Line l; + int randomIdx = ofRandom(7); + l.idx = idx++; + l.speaker = entry.value("speaker", ""); + l.sentence = entry.value("content", ""); + l.emotion = emotions[(int)randomIdx]; + script.push_back(l); + } + } else { + ofLogError() << "JSON file not found: " << filePath; + } + + // Random Check + if (!script.empty()) { + int randomIdx = ofRandom(script.size()); // returns float + int idx = static_cast(randomIdx); // convert to int + ofLog() << "Random line: " << script[idx].speaker << ": " << script[idx].sentence; + ofLog() << "Number of lines: " << script.size(); + } + + + // Set initial current line + currentLine = 0; +} + +void ofTeleprompter::drawText() { + ofSetColor(ofColor::white); + + // --- Display speaker and emotion centered at the top --- + std::string speakerText = "Speaker: " + script[currentLine].speaker; + std::string emotionText = "Emotion: " + script[currentLine].emotion; + + ofRectangle speakerBox = detailsFont.getStringBoundingBox(speakerText, 0, 0); + float speakerX = (ofGetWidth() - speakerBox.width) / 2.0f; + float speakerY = 128; // Top margin + + ofRectangle emotionBox = detailsFont.getStringBoundingBox(emotionText, 0, 0); + float emotionX = (ofGetWidth() - emotionBox.width) / 2.0f; + float emotionY = speakerY + speakerBox.height + 10; // 10px below speaker + + detailsFont.drawString(speakerText, speakerX, speakerY); + detailsFont.drawString(emotionText, emotionX, emotionY); + + // ------- + + ofSetColor(ofColor::yellow); + float margin = 128; // pixels + float maxWidth = ofGetWidth() - margin * 2; + std::string wrapped = wrapStringToWidth(displayedSentence, maxWidth); + + // Split wrapped into lines + std::vector lines; + std::istringstream iss(wrapped); + std::string line; + while (std::getline(iss, line)) { + lines.push_back(line); + } + + // Calculate total height for vertical centering + float totalHeight = lines.size() * textFont.getLineHeight(); + float startY = (ofGetHeight() / 2.0f) - (totalHeight / 2.0f); + + // Draw each line centered horizontally + for (size_t i = 0; i < lines.size(); ++i) { + ofRectangle bbox = textFont.getStringBoundingBox(lines[i], 0, 0); + float x = (ofGetWidth() - bbox.width) / 2.0f; + float y = startY + i * textFont.getLineHeight(); + textFont.drawString(lines[i], x, y); + } +} + +std::string ofTeleprompter::wrapStringToWidth(const std::string& text, float maxWidth) { + std::istringstream iss(text); + std::string word; + std::string wrapped, line; + while (iss >> word) { + std::string testLine = line.empty() ? word : line + " " + word; + ofRectangle bbox = textFont.getStringBoundingBox(testLine, 0, 0); + if (bbox.width > maxWidth && !line.empty()) { + wrapped += line + "\n"; + line = word; + } else { + line = testLine; + } + } + if (!line.empty()) wrapped += line; + return wrapped; +} + +void ofTeleprompter::updateCVData(int numOfFacesDetected) { + facesDetected = ofToString(numOfFacesDetected); } \ No newline at end of file diff --git a/src/ofTeleprompter.h b/src/ofTeleprompter.h index d295922..ab92048 100644 --- a/src/ofTeleprompter.h +++ b/src/ofTeleprompter.h @@ -1,7 +1,6 @@ #pragma once #include "ofMain.h" #include "ofxGui.h" -#include "csv.h" struct Line { int idx; @@ -18,6 +17,10 @@ class ofTeleprompter: public ofBaseApp{ void setupGUI(); void nextLinePressed(); void loadText(); + void drawText(); + void updateCVData(int numOfFacesDetected); + + std::string wrapStringToWidth(const std::string& text, float maxWidth); ofxPanel gui; @@ -37,7 +40,19 @@ class ofTeleprompter: public ofBaseApp{ /* script */ std::vector script; - std::string filePath = "text/Macebth.json"; + std::string filePath = "text/Macbeth.json"; + int currentLine = 0; + std::string currentLineString = "N/A"; + const char* emotions[7] = {"anger", "disgust", "fear", "happiness", "neutral", "sadness", "surprise"}; + + /* scrolling text */ + int currentLetterIndex = 0; + std::string currentSentence; + uint64_t lastWordTime = 0; + uint64_t wordDelay = 10; + std::string displayedSentence; + ofTrueTypeFont textFont; + ofTrueTypeFont detailsFont; private: