diff --git a/.github/workflows/build-all-on-macos.yml b/.github/workflows/build-all-on-macos.yml index d6db7b73..dc60a1e0 100644 --- a/.github/workflows/build-all-on-macos.yml +++ b/.github/workflows/build-all-on-macos.yml @@ -21,6 +21,17 @@ jobs: uses: actions/checkout@v4 - name: move fresh clone to OF_ROOT run: cp -r ../$(echo ${{ github.repository }} | cut -d '/' -f 2) ${{ vars.ADDONS_DIR }} + - name: download addon dependencies + working-directory: ${{ vars.ADDONS_DIR }} + run: bash ofxEmotiBit/scripts/download-dependencies.sh ${{ vars.ADDONS_DIR }} + - name: record dependency commits + working-directory: ${{ vars.ADDONS_DIR }} + run: bash ofxEmotiBit/scripts/record-dependencies.sh ${{ vars.ADDONS_DIR }} macos + - name: upload dependencies report + uses: actions/upload-artifact@v4 + with: + name: dependencies-report-macos + path: ${{ vars.OFXEMOTIBIT_DIR }}/dependencies-report-macos.txt build-oscilloscope-macos: needs: clone-macos runs-on: [self-hosted, macOS] @@ -54,8 +65,19 @@ jobs: echo "current working directory" && pwd cd EmotiBitFirmwareInstaller xcodebuild -project EmotiBitFirmwareInstaller.xcodeproj -scheme Release + build-slideplayer-macos: + needs: clone-macos + runs-on: [self-hosted, macOS] + environment: RUNNER-MACOS + steps: + - name: build using xcode + working-directory: ${{ vars.OFXEMOTIBIT_DIR }} + run: | + echo "current working directory" && pwd + cd EmotiBitSlidePlayer + xcodebuild -project EmotiBitSlidePlayer.xcodeproj -scheme Release upload-artifact-macos: - needs: [build-oscilloscope-macos, build-dataparser-macos, build-firmwareinstaller-macos] + needs: [build-oscilloscope-macos, build-dataparser-macos, build-firmwareinstaller-macos, build-slideplayer-macos] if: github.ref == 'refs/heads/dev' runs-on: [self-hosted, macOS] environment: RUNNER-MACOS @@ -76,6 +98,7 @@ jobs: mv EmotiBitOscilloscope/bin/EmotiBitOscilloscope.app stageRelease/EmotiBitSoftware-macos-${{ steps.get_version.outputs.version }} mv EmotiBitDataParser/bin/EmotiBitDataParser.app stageRelease/EmotiBitSoftware-macos-${{ steps.get_version.outputs.version }} mv EmotiBitFirmwareInstaller/bin/EmotiBitFirmwareInstaller.app stageRelease/EmotiBitSoftware-macos-${{ steps.get_version.outputs.version }} + mv EmotiBitSlidePlayer/bin/EmotiBitSlidePlayer.app stageRelease/EmotiBitSoftware-macos-${{ steps.get_version.outputs.version }} - name: copy SiLabs drivers working-directory: ${{ vars.ADDONS_DIR }} run: | @@ -92,3 +115,11 @@ jobs: with: name: EmotiBitSoftware_v${{ steps.get_version.outputs.version }}-macOS path: ${{ vars.OFXEMOTIBIT_DIR }}/stageRelease/EmotiBitSoftware-macos-${{ steps.get_version.outputs.version }}/ + cleanup-dependencies-macos: + needs: [build-oscilloscope-macos, build-dataparser-macos, build-firmwareinstaller-macos, build-slideplayer-macos] + runs-on: [self-hosted, macOS] + environment: RUNNER-MACOS + steps: + - name: remove cloned dependencies + working-directory: ${{ vars.ADDONS_DIR }} + run: bash ofxEmotiBit/scripts/download-dependencies.sh --cleanup ${{ vars.ADDONS_DIR }} diff --git a/.github/workflows/build-all-on-win.yaml b/.github/workflows/build-all-on-win.yaml index 202cdca6..f4d3b26d 100644 --- a/.github/workflows/build-all-on-win.yaml +++ b/.github/workflows/build-all-on-win.yaml @@ -25,6 +25,19 @@ jobs: run: | echo ${{ github.repository }} | cut -d '/' -f 2 cp -r ..\\$(echo ${{ github.repository }} | cut -d '/' -f 2) ${{ vars.ADDONS_DIR }} + - name: download addon dependencies + shell: bash + working-directory: ${{ vars.ADDONS_DIR }} + run: bash ofxEmotiBit/scripts/download-dependencies.sh "${{ vars.ADDONS_DIR }}" + - name: record dependency commits + shell: bash + working-directory: ${{ vars.ADDONS_DIR }} + run: bash ofxEmotiBit/scripts/record-dependencies.sh "${{ vars.ADDONS_DIR }}" windows + - name: upload dependencies report + uses: actions/upload-artifact@v4 + with: + name: dependencies-report-windows + path: ${{ vars.OFXEMOTIBIT_DIR }}/dependencies-report-windows.txt build-oscilloscope-windows: needs: clone-windows runs-on: [self-hosted, Windows] @@ -61,8 +74,20 @@ jobs: echo "current working directory" && pwd cd EmotiBitFirmwareInstaller MSBuild EmotiBitFirmwareInstaller.sln -t:Build -p:Configuration=Release + build-slideplayer-windows: + needs: clone-windows + runs-on: [self-hosted, Windows] + environment: RUNNER-WINDOWS + steps: + - name: build using MSBuild + shell: cmd + working-directory: ${{ vars.OFXEMOTIBIT_DIR }} + run: | + echo "current working directory" && pwd + cd EmotiBitSlidePlayer + MSBuild EmotiBitSlidePlayer.sln -t:Build -p:Configuration=Release build-installer-windows: - needs: [build-oscilloscope-windows, build-dataparser-windows, build-firmwareinstaller-windows] + needs: [build-oscilloscope-windows, build-dataparser-windows, build-firmwareinstaller-windows, build-slideplayer-windows] if: github.ref == 'refs/heads/dev' runs-on: [self-hosted, Windows] environment: RUNNER-WINDOWS @@ -147,4 +172,13 @@ jobs: uses: actions/upload-artifact@v4 with: name: EmotiBitSoftware_v${{ steps.get_version.outputs.version }}-Windows - path: ${{ vars.OFXEMOTIBIT_DIR }}\\stageRelease\\EmotiBitSoftware-Windows-${{ steps.get_version.outputs.version }} \ No newline at end of file + path: ${{ vars.OFXEMOTIBIT_DIR }}\\stageRelease\\EmotiBitSoftware-Windows-${{ steps.get_version.outputs.version }} + cleanup-dependencies-windows: + needs: [build-oscilloscope-windows, build-dataparser-windows, build-firmwareinstaller-windows, build-slideplayer-windows] + runs-on: [self-hosted, Windows] + environment: RUNNER-WINDOWS + steps: + - name: remove cloned dependencies + shell: bash + working-directory: ${{ vars.ADDONS_DIR }} + run: bash ofxEmotiBit/scripts/download-dependencies.sh --cleanup "${{ vars.ADDONS_DIR }}" \ No newline at end of file diff --git a/.github/workflows/create-draft-release.yml b/.github/workflows/create-draft-release.yml index 9c6aebd5..da31a8f7 100644 --- a/.github/workflows/create-draft-release.yml +++ b/.github/workflows/create-draft-release.yml @@ -23,6 +23,18 @@ jobs: echo "version=$VERSION" >> $GITHUB_OUTPUT echo "tag=v$VERSION" >> $GITHUB_OUTPUT + - name: Check release does not already exist + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG="${{ steps.get_version.outputs.tag }}" + if gh release view "$TAG" --repo "${{ github.repository }}" &>/dev/null; then + echo "ERROR: A release tagged '$TAG' already exists (draft, pre-release, or published)." + echo "Bump the version in src/ofxEmotiBitVersion.h before creating a new release." + exit 1 + fi + echo "No existing release found for '$TAG'. Proceeding." + - name: Download Mac artifacts uses: dawidd6/action-download-artifact@v6 with: @@ -43,11 +55,37 @@ jobs: skip_unpack: true continue-on-error: true + - name: Download macOS dependencies report + uses: dawidd6/action-download-artifact@v6 + with: + workflow: build-all-on-macos.yml + branch: dev + name: dependencies-report-macos + path: ./ + continue-on-error: true + + - name: Download Windows dependencies report + uses: dawidd6/action-download-artifact@v6 + with: + workflow: build-all-on-win.yaml + branch: dev + name: dependencies-report-windows + path: ./ + continue-on-error: true + + - name: Download Linux dependencies report + uses: dawidd6/action-download-artifact@v6 + with: + workflow: linux-workflows.yaml + branch: dev + name: dependencies-report-linux + path: ./ + continue-on-error: true + - name: Generate release notes - id: generate-release-notes run: | VERSION="${{ steps.get_version.outputs.version }}" - RELEASE_NOTES=$(cat < release_notes.md << END_OF_NOTES # New features and bug fixes # Firmware Installed by FirmwareInstaller @@ -73,12 +111,17 @@ jobs: #### Linux - Download the source code linked below. Follow the steps in the [ReadMe](https://github.com/EmotiBit/ofxEmotiBit#readme). - + # Dependencies + See attached **dependencies-report.zip** for the full dependency report per platform. END_OF_NOTES - ) - echo "release_notes<> $GITHUB_OUTPUT - echo "$RELEASE_NOTES" >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT + + - name: Zip dependency reports + run: | + zip dependencies-report.zip \ + dependencies-report-macos.txt \ + dependencies-report-windows.txt \ + dependencies-report-linux.txt \ + 2>/dev/null || true - name: Create config.txt id: create-config @@ -98,16 +141,16 @@ jobs: echo "Config file created successfully" - name: Create Draft Release - id: create-release uses: softprops/action-gh-release@v1 with: draft: true tag_name: ${{ steps.get_version.outputs.tag }} name: Draft Release ${{ steps.get_version.outputs.tag }} - body: ${{ steps.generate-release-notes.outputs.release_notes }} + body_path: release_notes.md files: | EmotiBitSoftware_v${{ steps.get_version.outputs.version }}-macOS.zip EmotiBitSoftware_v${{ steps.get_version.outputs.version }}-Windows.zip config.txt + dependencies-report.zip env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/linux-workflows.yaml b/.github/workflows/linux-workflows.yaml index 33257263..6eeb8ede 100644 --- a/.github/workflows/linux-workflows.yaml +++ b/.github/workflows/linux-workflows.yaml @@ -21,6 +21,17 @@ jobs: uses: actions/checkout@v4 - name: move fresh clone to OF_ROOT run: cp -r ../$(echo ${{ github.repository }} | cut -d '/' -f 2) ${{ vars.ADDONS_DIR }} + - name: download addon dependencies + working-directory: ${{ vars.ADDONS_DIR }} + run: bash ofxEmotiBit/scripts/download-dependencies.sh ${{ vars.ADDONS_DIR }} + - name: record dependency commits + working-directory: ${{ vars.ADDONS_DIR }} + run: bash ofxEmotiBit/scripts/record-dependencies.sh ${{ vars.ADDONS_DIR }} linux + - name: upload dependencies report + uses: actions/upload-artifact@v4 + with: + name: dependencies-report-linux + path: ${{ vars.OFXEMOTIBIT_DIR }}/dependencies-report-linux.txt build-oscilloscope-linux: needs: clone-linux uses: ./.github/workflows/reusable-workflow-build-oscilloscope-linux.yml @@ -36,3 +47,12 @@ jobs: test-dataparser-linux: needs: build-dataparser-linux uses: ./.github/workflows/reusable-workflow-test-dataParser-linux.yml + + cleanup-dependencies-linux: + needs: [test-oscilloscope-linux, test-dataparser-linux] + runs-on: [self-hosted, Linux] + environment: RUNNER-LINUX + steps: + - name: remove cloned dependencies + working-directory: ${{ vars.ADDONS_DIR }} + run: bash ofxEmotiBit/scripts/download-dependencies.sh --cleanup ${{ vars.ADDONS_DIR }} diff --git a/.gitignore b/.gitignore index 55f085a4..3a07fbdd 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,12 @@ *.a *.lib +# build folders +**/build/ + +# docs +**/docs/ + # Executables *.exe *.out @@ -60,3 +66,6 @@ EmotiBitInstaller/redist/ # testing tests/EmotiBitDataParser/sample_data/2025-06-27_09-56-59-929206*.csv + +# macOS +**/.DS_Store diff --git a/EmotiBitInstaller/EmotiBitInstaller.iss b/EmotiBitInstaller/EmotiBitInstaller.iss index fb55a2b5..b0132097 100644 --- a/EmotiBitInstaller/EmotiBitInstaller.iss +++ b/EmotiBitInstaller/EmotiBitInstaller.iss @@ -57,6 +57,13 @@ Source: "..\EmotiBitFirmwareInstaller\bin\data\esp32\*"; DestDir: "{app}\EmotiBi Source: "..\EmotiBitFirmwareInstaller\bin\data\exec\win\*"; DestDir: "{app}\EmotiBit FirmwareInstaller\data\exec\win" Source: "..\EmotiBitFirmwareInstaller\bin\data\instructions\*.jpg"; DestDir: "{app}\EmotiBit FirmwareInstaller\data\instructions" +; EmotiBit SlidePlayer +Source: "..\EmotiBitSlidePlayer\bin\EmotiBitSlidePlayer.exe"; DestDir: "{app}\EmotiBit SlidePlayer" +Source: "..\EmotiBitSlidePlayer\bin\*.dll"; DestDir: "{app}\EmotiBit SlidePlayer" +Source: "..\EmotiBitSlidePlayer\bin\data\emotibitSlidePlayerSettings.json"; DestDir: "{app}\EmotiBit SlidePlayer\data" +; Example slides +Source: "..\EmotiBitSlidePlayer\bin\data\example_slides\*"; DestDir: "{app}\EmotiBit SlidePlayer\data\example_slides"; Flags: recursesubdirs + ; VC++ 2017 Redistributable Source: "redist\vc_redist.x64.exe"; DestDir: "{tmp}"; Flags: deleteafterinstall @@ -68,6 +75,7 @@ Source: "..\EmotiBitIcons\icoFiles\EmotiBitLogo.ico"; DestDir: "{app}" Name: "{group}\EmotiBit Oscilloscope"; Filename: "{app}\EmotiBit Oscilloscope\EmotiBitOscilloscope.exe"; WorkingDir: "{app}\EmotiBit Oscilloscope" Name: "{group}\EmotiBit DataParser"; Filename: "{app}\EmotiBit DataParser\EmotiBitDataParser.exe"; WorkingDir: "{app}\EmotiBit DataParser" Name: "{group}\EmotiBit FirmwareInstaller"; Filename: "{app}\EmotiBit FirmwareInstaller\EmotiBitFirmwareInstaller.exe"; WorkingDir: "{app}\EmotiBit FirmwareInstaller" +Name: "{group}\EmotiBit SlidePlayer"; Filename: "{app}\EmotiBit SlidePlayer\EmotiBitSlidePlayer.exe"; WorkingDir: "{app}\EmotiBit SlidePlayer" [Run] ; Install VC++ 2017 Redistributable if not already installed @@ -79,6 +87,7 @@ Filename: "{tmp}\vc_redist.x64.exe"; Parameters: "/passive /norestart"; StatusMs Type: filesandordirs; Name: "{app}\EmotiBit Oscilloscope" Type: filesandordirs; Name: "{app}\EmotiBit DataParser" Type: filesandordirs; Name: "{app}\EmotiBit FirmwareInstaller" +Type: filesandordirs; Name: "{app}\EmotiBit SlidePlayer" ; Delete the parent EmotiBit folder if empty after above deletions Type: dirifempty; Name: "{app}" diff --git a/EmotiBitOscilloscope/bin/data/ofxOscilloscopeSettings.xml b/EmotiBitOscilloscope/bin/data/ofxOscilloscopeSettings.xml index 7f06d20b..7120885b 100644 --- a/EmotiBitOscilloscope/bin/data/ofxOscilloscopeSettings.xml +++ b/EmotiBitOscilloscope/bin/data/ofxOscilloscopeSettings.xml @@ -1,3 +1,8 @@ + + 1500 + 900 + + 0.000000000 55.000000000 diff --git a/EmotiBitOscilloscope/src/ofApp.cpp b/EmotiBitOscilloscope/src/ofApp.cpp index 49f31f38..526a784e 100644 --- a/EmotiBitOscilloscope/src/ofApp.cpp +++ b/EmotiBitOscilloscope/src/ofApp.cpp @@ -23,6 +23,7 @@ void ofApp::setup() { emotiBitWiFi.begin(); // Startup WiFi connectivity emotiBitWiFi.attachAppQ(&auxCtrlQ); // pass the main application instruction q to the wifi controller timeWindowOnSetup = 10; // set timeWindow for setup (in seconds) + loadWindowSettings(); setupGui(); setupOscilloscopes(); @@ -220,6 +221,11 @@ void ofApp::removeDataStream(std::string typetag) //-------------------------------------------------------------- void ofApp::draw() { + if (background_image_.isAllocated()) + { + ofSetColor(255); + background_image_.draw(0, 0, ofGetWindowWidth(), ofGetWindowHeight()); + } drawOscilloscopes(); drawConsole(); } @@ -1125,6 +1131,25 @@ void ofApp::processSlowResponseMessage(vector splitPacket) } } +void ofApp::loadWindowSettings() +{ + ofxXmlSettings xml; + if (xml.loadFile(ofToDataPath("ofxOscilloscopeSettings.xml"))) + { + xml.pushTag("window"); + int w = xml.getValue("width", 1500); + int h = xml.getValue("height", 900); + ofSetWindowShape(w, h); + + string bgPath = xml.getValue("backgroundImagePath", ""); + if (!bgPath.empty()) + { + background_image_.load(ofToDataPath(bgPath)); + } + xml.popTag(); + } +} + void ofApp::setupGui() { ofSetWindowTitle("EmotiBit Oscilloscope (v" + ofxEmotiBitVersion + ")"); diff --git a/EmotiBitOscilloscope/src/ofApp.h b/EmotiBitOscilloscope/src/ofApp.h index 8d7a86e6..5581a90c 100644 --- a/EmotiBitOscilloscope/src/ofApp.h +++ b/EmotiBitOscilloscope/src/ofApp.h @@ -77,6 +77,7 @@ class ofApp : public ofBaseApp { // ToDo: This function is marked to be removed when we complete our move to xmlFileSettings. + void loadWindowSettings(); void updatePlotAttributeLists(std::string settingsFile = "ofxOscilloscopeSettings.xml"); void updateTypeTagList(); string loadTextFile(string filePath); @@ -159,6 +160,7 @@ class ofApp : public ofBaseApp { EmotiBitPacket::TypeTag::SKIN_CONDUCTANCE_RESPONSE_RISE_TIME, 0) }; + ofImage background_image_; vector scopeWins; unordered_map> plotIdIndexes; vector>> typeTags; diff --git a/EmotiBitSlidePlayer/.clang-format b/EmotiBitSlidePlayer/.clang-format new file mode 100644 index 00000000..61427fb1 --- /dev/null +++ b/EmotiBitSlidePlayer/.clang-format @@ -0,0 +1,8 @@ +BasedOnStyle: Google +BreakBeforeBraces: Allman +IndentWidth: 4 +UseTab: Never +AllowShortFunctionsOnASingleLine: None +AllowShortBlocksOnASingleLine: Never +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false diff --git a/EmotiBitSlidePlayer/.clang-tidy b/EmotiBitSlidePlayer/.clang-tidy new file mode 100644 index 00000000..55ac74d2 --- /dev/null +++ b/EmotiBitSlidePlayer/.clang-tidy @@ -0,0 +1,55 @@ +Checks: > + readability-identifier-naming, + readability-identifier-length, + modernize-use-nullptr, + -modernize-use-override, + modernize-use-default-member-init, + bugprone-*, + -bugprone-easily-swappable-parameters + +CheckOptions: + # Local variables: snake_case, no prefix + - key: readability-identifier-naming.LocalVariableCase + value: lower_case + - key: readability-identifier-naming.LocalVariablePrefix + value: '' + + # Parameters: snake_case, no prefix + - key: readability-identifier-naming.ParameterCase + value: lower_case + - key: readability-identifier-naming.ParameterPrefix + value: '' + - key: readability-identifier-naming.ParameterMinLength + value: '1' + + # Member variables: snake_case with trailing underscore + - key: readability-identifier-naming.MemberCase + value: lower_case + - key: readability-identifier-naming.MemberSuffix + value: '_' + + # Functions and methods: camelCase (Google style) + - key: readability-identifier-naming.FunctionCase + value: camelBack + - key: readability-identifier-naming.MethodCase + value: camelBack + + # Classes and structs: CamelCase + - key: readability-identifier-naming.ClassCase + value: CamelCase + - key: readability-identifier-naming.StructCase + value: CamelCase + + # Constants: kCamelCase (Google style) + - key: readability-identifier-naming.ConstantCase + value: CamelCase + - key: readability-identifier-naming.ConstantPrefix + value: 'k' + + # Allow single-character names (e.g. loop counters, x/y coords) + - key: readability-identifier-length.MinimumVariableNameLength + value: '1' + - key: readability-identifier-length.MinimumParameterNameLength + value: '1' + - key: readability-identifier-length.MinimumLoopCounterNameLength + value: '1' diff --git a/EmotiBitSlidePlayer/CMakeLists.txt b/EmotiBitSlidePlayer/CMakeLists.txt new file mode 100644 index 00000000..98e1bdf2 --- /dev/null +++ b/EmotiBitSlidePlayer/CMakeLists.txt @@ -0,0 +1,83 @@ +cmake_minimum_required(VERSION 3.20) + +set(CMAKE_OSX_DEPLOYMENT_TARGET "13.0" CACHE STRING "Minimum macOS version") +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +project(EmotiBitSlidePlayer) + +# ── Shared OF + addon modules ───────────────────────────────────────────────── +set(_CMAKE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../cmake") +include("${_CMAKE_DIR}/openFrameworks.cmake") +include("${_CMAKE_DIR}/addons/ofxJSON.cmake") + +# ── Shared app sources (no main.cpp — reused by both app and tests) ─────────── +file(GLOB MM_SOURCES "src/*.mm") +set(OF_APP_SOURCES + src/ofApp.cpp + ${MM_SOURCES} + ${ADDON_OFXJSON_SOURCES} +) + +# ── Main application ────────────────────────────────────────────────────────── +add_executable(${PROJECT_NAME} MACOSX_BUNDLE src/main.cpp ${OF_APP_SOURCES}) + +of_configure_app(${PROJECT_NAME} 23) + +target_include_directories(${PROJECT_NAME} PRIVATE + ${ADDON_OFXJSON_INCLUDE_DIRS} +) + +set_target_properties(${PROJECT_NAME} PROPERTIES + MACOSX_BUNDLE_BUNDLE_NAME "EmotiBitSlidePlayer" +) + +# ── Copy settings + slides to app bundle Resources ──────────────────────────── +set(BUNDLE_RESOURCES + "${CMAKE_CURRENT_SOURCE_DIR}/bin/${PROJECT_NAME}.app/Contents/Resources") +add_custom_target(copy_settings ALL + COMMAND ${CMAKE_COMMAND} -E make_directory "${BUNDLE_RESOURCES}" + COMMAND ${CMAKE_COMMAND} -E copy + "${CMAKE_CURRENT_SOURCE_DIR}/bin/data/emotibitSlidePlayerSettings.json" + "${BUNDLE_RESOURCES}/emotibitSlidePlayerSettings.json" + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_CURRENT_SOURCE_DIR}/bin/data/example_slides" + "${BUNDLE_RESOURCES}/example_slides" + COMMENT "Copying settings and example slides to app bundle Resources" +) +add_dependencies(copy_settings ${PROJECT_NAME}) + +# ── Tests ───────────────────────────────────────────────────────────────────── +enable_testing() + +include(FetchContent) +FetchContent_Declare( + Catch2 + GIT_REPOSITORY https://github.com/catchorg/Catch2.git + GIT_TAG v3.5.2 +) +FetchContent_MakeAvailable(Catch2) + +add_executable(slideShow_tests + tests/main.cpp + tests/test_parseSettings.cpp + tests/test_updateCurrentState.cpp + ${OF_APP_SOURCES} +) + +of_configure_app(slideShow_tests 23) + +# Keep test binary in the build dir, not bin/ — it can't run as a subprocess +# with no display context (links against AppKit/Cocoa via OF) +set_target_properties(slideShow_tests PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" +) + +target_include_directories(slideShow_tests PRIVATE + ${ADDON_OFXJSON_INCLUDE_DIRS} +) + +target_link_libraries(slideShow_tests PRIVATE Catch2::Catch2) + +include(Catch) +# PRE_TEST: discover tests when ctest runs, not at build time +catch_discover_tests(slideShow_tests DISCOVERY_MODE PRE_TEST) diff --git a/EmotiBitSlidePlayer/Doxyfile b/EmotiBitSlidePlayer/Doxyfile new file mode 100644 index 00000000..735ffbf2 --- /dev/null +++ b/EmotiBitSlidePlayer/Doxyfile @@ -0,0 +1,27 @@ +PROJECT_NAME = "mock_slideShow" +PROJECT_BRIEF = "EmotiBit slide player application" +OUTPUT_DIRECTORY = docs + +# Input +INPUT = src +FILE_PATTERNS = *.h *.cpp +RECURSIVE = NO + +# Enforce documentation +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_AS_ERROR = YES + +# Only document public members in headers +EXTRACT_ALL = NO +EXTRACT_PRIVATE = NO +EXTRACT_STATIC = YES + +# Doxygen style +JAVADOC_AUTOBRIEF = NO +QT_AUTOBRIEF = NO +MULTILINE_CPP_IS_BRIEF = NO + +# Output formats +GENERATE_HTML = YES +GENERATE_LATEX = NO diff --git a/EmotiBitSlidePlayer/EmotiBitSlidePlayer.sln b/EmotiBitSlidePlayer/EmotiBitSlidePlayer.sln new file mode 100644 index 00000000..29133d68 --- /dev/null +++ b/EmotiBitSlidePlayer/EmotiBitSlidePlayer.sln @@ -0,0 +1,35 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EmotiBitSlidePlayer", "EmotiBitSlidePlayer.vcxproj", "{7FD42DF7-442E-479A-BA76-D0022F99702A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "openframeworksLib", "..\..\..\libs\openFrameworksCompiled\project\vs\openframeworksLib.vcxproj", "{5837595D-ACA9-485C-8E76-729040CE4B0B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7FD42DF7-442E-479A-BA76-D0022F99702A}.Debug|Win32.ActiveCfg = Debug|Win32 + {7FD42DF7-442E-479A-BA76-D0022F99702A}.Debug|Win32.Build.0 = Debug|Win32 + {7FD42DF7-442E-479A-BA76-D0022F99702A}.Debug|x64.ActiveCfg = Debug|x64 + {7FD42DF7-442E-479A-BA76-D0022F99702A}.Debug|x64.Build.0 = Debug|x64 + {7FD42DF7-442E-479A-BA76-D0022F99702A}.Release|Win32.ActiveCfg = Release|Win32 + {7FD42DF7-442E-479A-BA76-D0022F99702A}.Release|Win32.Build.0 = Release|Win32 + {7FD42DF7-442E-479A-BA76-D0022F99702A}.Release|x64.ActiveCfg = Release|x64 + {7FD42DF7-442E-479A-BA76-D0022F99702A}.Release|x64.Build.0 = Release|x64 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Debug|Win32.ActiveCfg = Debug|Win32 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Debug|Win32.Build.0 = Debug|Win32 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Debug|x64.ActiveCfg = Debug|x64 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Debug|x64.Build.0 = Debug|x64 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Release|Win32.ActiveCfg = Release|Win32 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Release|Win32.Build.0 = Release|Win32 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Release|x64.ActiveCfg = Release|x64 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/EmotiBitSlidePlayer/EmotiBitSlidePlayer.vcxproj b/EmotiBitSlidePlayer/EmotiBitSlidePlayer.vcxproj new file mode 100644 index 00000000..e1c9a8d6 --- /dev/null +++ b/EmotiBitSlidePlayer/EmotiBitSlidePlayer.vcxproj @@ -0,0 +1,213 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + $([Microsoft.Build.Utilities.ToolLocationHelper]::GetLatestSDKTargetPlatformVersion('Windows', '10.0')) + 10.0 + $(WindowsTargetPlatformVersion) + + + {7FD42DF7-442E-479A-BA76-D0022F99702A} + Win32Proj + EmotiBitSlidePlayer + + + + Application + Unicode + v143 + + + Application + Unicode + v143 + + + Application + Unicode + true + v143 + + + Application + Unicode + true + v143 + + + + + + + + + + + + + + + + + + + + + bin\ + obj\$(Platform)\$(Configuration)\ + $(ProjectName)_debug + true + true + + + bin\ + obj\$(Platform)\$(Configuration)\ + $(ProjectName)_debug + true + true + + + bin\ + obj\$(Platform)\$(Configuration)\ + false + + + bin\ + obj\$(Platform)\$(Configuration)\ + false + + + + Disabled + EnableFastChecks + %(PreprocessorDefinitions) + MultiThreadedDebugDLL + Level3 + %(AdditionalIncludeDirectories);..\..\..\addons\ofxJSON\libs;..\..\..\addons\ofxJSON\libs\jsoncpp;..\..\..\addons\ofxJSON\libs\jsoncpp\include;..\..\..\addons\ofxJSON\libs\jsoncpp\include\json;..\..\..\addons\ofxJSON\libs\jsoncpp\src;..\..\..\addons\ofxJSON\src + CompileAsCpp + $(IntDir)%(RelativeDir) + + + true + Console + false + %(AdditionalDependencies) + %(AdditionalLibraryDirectories) + + + + + + Disabled + EnableFastChecks + %(PreprocessorDefinitions) + MultiThreadedDebugDLL + Level3 + %(AdditionalIncludeDirectories);..\..\..\addons\ofxJSON\libs;..\..\..\addons\ofxJSON\libs\jsoncpp;..\..\..\addons\ofxJSON\libs\jsoncpp\include;..\..\..\addons\ofxJSON\libs\jsoncpp\include\json;..\..\..\addons\ofxJSON\libs\jsoncpp\src;..\..\..\addons\ofxJSON\src + CompileAsCpp + true + $(IntDir)%(RelativeDir) + + + true + Console + false + %(AdditionalDependencies) + %(AdditionalLibraryDirectories) + + + + + + false + %(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + %(AdditionalIncludeDirectories);..\..\..\addons\ofxJSON\libs;..\..\..\addons\ofxJSON\libs\jsoncpp;..\..\..\addons\ofxJSON\libs\jsoncpp\include;..\..\..\addons\ofxJSON\libs\jsoncpp\include\json;..\..\..\addons\ofxJSON\libs\jsoncpp\src;..\..\..\addons\ofxJSON\src + CompileAsCpp + true + $(IntDir)%(RelativeDir) + + + false + false + Console + true + true + false + %(AdditionalDependencies) + %(AdditionalLibraryDirectories) + + + + + + false + %(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + %(AdditionalIncludeDirectories);..\..\..\addons\ofxJSON\libs;..\..\..\addons\ofxJSON\libs\jsoncpp;..\..\..\addons\ofxJSON\libs\jsoncpp\include;..\..\..\addons\ofxJSON\libs\jsoncpp\include\json;..\..\..\addons\ofxJSON\libs\jsoncpp\src;..\..\..\addons\ofxJSON\src + CompileAsCpp + $(IntDir)%(RelativeDir) + + + false + false + Console + true + true + false + %(AdditionalDependencies) + %(AdditionalLibraryDirectories) + + + + + + + + + + + + + + + + + + + {5837595d-aca9-485c-8e76-729040ce4b0b} + + + + + /D_DEBUG %(AdditionalOptions) + /D_DEBUG %(AdditionalOptions) + $(OF_ROOT)\libs\openFrameworksCompiled\project\vs + + + + + + + + + \ No newline at end of file diff --git a/EmotiBitSlidePlayer/EmotiBitSlidePlayer.vcxproj.filters b/EmotiBitSlidePlayer/EmotiBitSlidePlayer.vcxproj.filters new file mode 100644 index 00000000..99443a2c --- /dev/null +++ b/EmotiBitSlidePlayer/EmotiBitSlidePlayer.vcxproj.filters @@ -0,0 +1,66 @@ + + + + + src + + + src + + + addons\ofxJSON\src + + + addons\ofxJSON\libs\jsoncpp\src + + + + + {d8376475-7454-4a24-b08a-aac121d3ad6f} + + + {71834F65-F3A9-211E-73B8-DC85} + + + {CF86E01B-D8D7-A112-88F3-5883} + + + {CE83AFE9-FE4A-682E-D5C9-50B4} + + + {5302B3F8-ECDD-0465-334A-F39E} + + + {87D6C548-39D9-FD9F-093E-9F4E} + + + {0B6CA981-484E-6E8C-776C-7298} + + + {5FF260B2-1497-3EFE-19D5-40DB} + + + {6D569A5B-6719-AEFD-C08E-5CE5} + + + + + src + + + addons\ofxJSON\src + + + addons\ofxJSON\src + + + addons\ofxJSON\libs\jsoncpp\include\json + + + addons\ofxJSON\libs\jsoncpp\include\json + + + + + + diff --git a/EmotiBitSlidePlayer/EmotiBitSlidePlayer.xcodeproj/project.pbxproj b/EmotiBitSlidePlayer/EmotiBitSlidePlayer.xcodeproj/project.pbxproj new file mode 100644 index 00000000..db87a840 --- /dev/null +++ b/EmotiBitSlidePlayer/EmotiBitSlidePlayer.xcodeproj/project.pbxproj @@ -0,0 +1,990 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 8EF01BA52F7876C700BF0971 /* emotibitSlidePlayerSettings.json in Copy Settings and Example Slides */ = {isa = PBXBuildFile; fileRef = 8EF01BA42F7876C700BF0971 /* emotibitSlidePlayerSettings.json */; }; + 8EF01BA72F7876DD00BF0971 /* example_slides in Copy Settings and Example Slides */ = {isa = PBXBuildFile; fileRef = 8EF01BA62F7876DD00BF0971 /* example_slides */; }; + BEDFEE7400C58EA4E412B757 /* ofxJSONElement.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F2B099E6BD1199664C48B177 /* ofxJSONElement.cpp */; }; + E4B69E200A3A1BDC003C02F2 /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E4B69E1D0A3A1BDC003C02F2 /* main.cpp */; }; + E4B69E210A3A1BDC003C02F2 /* ofApp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E4B69E1E0A3A1BDC003C02F2 /* ofApp.cpp */; }; + FB84AAF8D1B7A95266DB5C09 /* jsoncpp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 21BDE665988474F1B1F4D302 /* jsoncpp.cpp */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 8EF01BA32F78768F00BF0971 /* Copy Settings and Example Slides */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 7; + files = ( + 8EF01BA72F7876DD00BF0971 /* example_slides in Copy Settings and Example Slides */, + 8EF01BA52F7876C700BF0971 /* emotibitSlidePlayerSettings.json in Copy Settings and Example Slides */, + ); + name = "Copy Settings and Example Slides"; + runOnlyForDeploymentPostprocessing = 0; + }; + E4C2427710CC5ABF004149E2 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1645F56257269CD0356320BD /* ofxJSON.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.c.h; fileEncoding = 4; name = ofxJSON.h; path = ../../../addons/ofxJSON/src/ofxJSON.h; sourceTree = SOURCE_ROOT; }; + 21BDE665988474F1B1F4D302 /* jsoncpp.cpp */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.cpp; fileEncoding = 4; name = jsoncpp.cpp; path = ../../../addons/ofxJSON/libs/jsoncpp/src/jsoncpp.cpp; sourceTree = SOURCE_ROOT; }; + 26A541233BC6F736E758F718 /* ofxJSONElement.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.c.h; fileEncoding = 4; name = ofxJSONElement.h; path = ../../../addons/ofxJSON/src/ofxJSONElement.h; sourceTree = SOURCE_ROOT; }; + 2C7CF000B7B4F782C187C353 /* json.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.c.h; fileEncoding = 4; name = json.h; path = ../../../addons/ofxJSON/libs/jsoncpp/include/json/json.h; sourceTree = SOURCE_ROOT; }; + 61313493CDB52744E22A604D /* json-forwards.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.c.h; fileEncoding = 4; name = "json-forwards.h"; path = "../../../addons/ofxJSON/libs/jsoncpp/include/json/json-forwards.h"; sourceTree = SOURCE_ROOT; }; + 8EF01BA42F7876C700BF0971 /* emotibitSlidePlayerSettings.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; name = emotibitSlidePlayerSettings.json; path = bin/data/emotibitSlidePlayerSettings.json; sourceTree = ""; }; + 8EF01BA62F7876DD00BF0971 /* example_slides */ = {isa = PBXFileReference; lastKnownFileType = folder; name = example_slides; path = bin/data/example_slides; sourceTree = ""; }; + E42962AC2163EDD300A6A9E2 /* ofCamera.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofCamera.cpp; path = ../../../libs/openFrameworks/3d/ofCamera.cpp; sourceTree = SOURCE_ROOT; }; + E42962AD2163EDD300A6A9E2 /* ofMesh.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofMesh.h; path = ../../../libs/openFrameworks/3d/ofMesh.h; sourceTree = SOURCE_ROOT; }; + E42962AE2163EDD300A6A9E2 /* ofNode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofNode.h; path = ../../../libs/openFrameworks/3d/ofNode.h; sourceTree = SOURCE_ROOT; }; + E42962AF2163EDD300A6A9E2 /* ofNode.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofNode.cpp; path = ../../../libs/openFrameworks/3d/ofNode.cpp; sourceTree = SOURCE_ROOT; }; + E42962B02163EDD300A6A9E2 /* of3dPrimitives.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = of3dPrimitives.cpp; path = ../../../libs/openFrameworks/3d/of3dPrimitives.cpp; sourceTree = SOURCE_ROOT; }; + E42962B12163EDD300A6A9E2 /* of3dUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = of3dUtils.h; path = ../../../libs/openFrameworks/3d/of3dUtils.h; sourceTree = SOURCE_ROOT; }; + E42962B22163EDD300A6A9E2 /* ofEasyCam.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofEasyCam.cpp; path = ../../../libs/openFrameworks/3d/ofEasyCam.cpp; sourceTree = SOURCE_ROOT; }; + E42962B32163EDD300A6A9E2 /* ofCamera.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofCamera.h; path = ../../../libs/openFrameworks/3d/ofCamera.h; sourceTree = SOURCE_ROOT; }; + E42962B42163EDD300A6A9E2 /* ofMesh.inl */ = {isa = PBXFileReference; lastKnownFileType = text; name = ofMesh.inl; path = ../../../libs/openFrameworks/3d/ofMesh.inl; sourceTree = SOURCE_ROOT; }; + E42962B52163EDD300A6A9E2 /* of3dUtils.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = of3dUtils.cpp; path = ../../../libs/openFrameworks/3d/of3dUtils.cpp; sourceTree = SOURCE_ROOT; }; + E42962B62163EDD300A6A9E2 /* of3dPrimitives.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = of3dPrimitives.h; path = ../../../libs/openFrameworks/3d/of3dPrimitives.h; sourceTree = SOURCE_ROOT; }; + E42962B72163EDD300A6A9E2 /* ofEasyCam.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofEasyCam.h; path = ../../../libs/openFrameworks/3d/ofEasyCam.h; sourceTree = SOURCE_ROOT; }; + E42962BA2163EDD300A6A9E2 /* ofAVFoundationGrabber.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofAVFoundationGrabber.h; path = ../../../libs/openFrameworks/video/ofAVFoundationGrabber.h; sourceTree = SOURCE_ROOT; }; + E42962BB2163EDD300A6A9E2 /* ofVideoPlayer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofVideoPlayer.h; path = ../../../libs/openFrameworks/video/ofVideoPlayer.h; sourceTree = SOURCE_ROOT; }; + E42962BC2163EDD300A6A9E2 /* ofAVFoundationPlayer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofAVFoundationPlayer.h; path = ../../../libs/openFrameworks/video/ofAVFoundationPlayer.h; sourceTree = SOURCE_ROOT; }; + E42962BD2163EDD300A6A9E2 /* ofQtUtils.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofQtUtils.cpp; path = ../../../libs/openFrameworks/video/ofQtUtils.cpp; sourceTree = SOURCE_ROOT; }; + E42962BE2163EDD300A6A9E2 /* ofQTKitGrabber.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = ofQTKitGrabber.mm; path = ../../../libs/openFrameworks/video/ofQTKitGrabber.mm; sourceTree = SOURCE_ROOT; }; + E42962BF2163EDD300A6A9E2 /* ofQuickTimeGrabber.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofQuickTimeGrabber.cpp; path = ../../../libs/openFrameworks/video/ofQuickTimeGrabber.cpp; sourceTree = SOURCE_ROOT; }; + E42962C12163EDD300A6A9E2 /* ofAVFoundationVideoPlayer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofAVFoundationVideoPlayer.h; path = ../../../libs/openFrameworks/video/ofAVFoundationVideoPlayer.h; sourceTree = SOURCE_ROOT; }; + E42962C22163EDD300A6A9E2 /* ofAVFoundationGrabber.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = ofAVFoundationGrabber.mm; path = ../../../libs/openFrameworks/video/ofAVFoundationGrabber.mm; sourceTree = SOURCE_ROOT; }; + E42962C32163EDD300A6A9E2 /* ofQTKitMovieRenderer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofQTKitMovieRenderer.h; path = ../../../libs/openFrameworks/video/ofQTKitMovieRenderer.h; sourceTree = SOURCE_ROOT; }; + E42962C42163EDD300A6A9E2 /* ofVideoPlayer.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofVideoPlayer.cpp; path = ../../../libs/openFrameworks/video/ofVideoPlayer.cpp; sourceTree = SOURCE_ROOT; }; + E42962C52163EDD300A6A9E2 /* ofQTKitGrabber.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofQTKitGrabber.h; path = ../../../libs/openFrameworks/video/ofQTKitGrabber.h; sourceTree = SOURCE_ROOT; }; + E42962C72163EDD300A6A9E2 /* ofQuickTimeGrabber.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofQuickTimeGrabber.h; path = ../../../libs/openFrameworks/video/ofQuickTimeGrabber.h; sourceTree = SOURCE_ROOT; }; + E42962C92163EDD300A6A9E2 /* ofQuickTimePlayer.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofQuickTimePlayer.cpp; path = ../../../libs/openFrameworks/video/ofQuickTimePlayer.cpp; sourceTree = SOURCE_ROOT; }; + E42962CA2163EDD300A6A9E2 /* ofVideoGrabber.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofVideoGrabber.h; path = ../../../libs/openFrameworks/video/ofVideoGrabber.h; sourceTree = SOURCE_ROOT; }; + E42962CB2163EDD300A6A9E2 /* ofQTKitPlayer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofQTKitPlayer.h; path = ../../../libs/openFrameworks/video/ofQTKitPlayer.h; sourceTree = SOURCE_ROOT; }; + E42962CD2163EDD300A6A9E2 /* ofQtUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofQtUtils.h; path = ../../../libs/openFrameworks/video/ofQtUtils.h; sourceTree = SOURCE_ROOT; }; + E42962CE2163EDD300A6A9E2 /* ofAVFoundationPlayer.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = ofAVFoundationPlayer.mm; path = ../../../libs/openFrameworks/video/ofAVFoundationPlayer.mm; sourceTree = SOURCE_ROOT; }; + E42962CF2163EDD300A6A9E2 /* ofQuickTimePlayer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofQuickTimePlayer.h; path = ../../../libs/openFrameworks/video/ofQuickTimePlayer.h; sourceTree = SOURCE_ROOT; }; + E42962D02163EDD300A6A9E2 /* ofVideoGrabber.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofVideoGrabber.cpp; path = ../../../libs/openFrameworks/video/ofVideoGrabber.cpp; sourceTree = SOURCE_ROOT; }; + E42962D52163EDD300A6A9E2 /* ofQTKitPlayer.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = ofQTKitPlayer.mm; path = ../../../libs/openFrameworks/video/ofQTKitPlayer.mm; sourceTree = SOURCE_ROOT; }; + E42962D62163EDD300A6A9E2 /* ofQTKitMovieRenderer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = ofQTKitMovieRenderer.m; path = ../../../libs/openFrameworks/video/ofQTKitMovieRenderer.m; sourceTree = SOURCE_ROOT; }; + E42962D72163EDD300A6A9E2 /* ofAVFoundationVideoPlayer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = ofAVFoundationVideoPlayer.m; path = ../../../libs/openFrameworks/video/ofAVFoundationVideoPlayer.m; sourceTree = SOURCE_ROOT; }; + E42962D92163EDD300A6A9E2 /* ofVideoBaseTypes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofVideoBaseTypes.h; path = ../../../libs/openFrameworks/video/ofVideoBaseTypes.h; sourceTree = SOURCE_ROOT; }; + E42962DB2163EDD300A6A9E2 /* ofParameterGroup.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofParameterGroup.h; path = ../../../libs/openFrameworks/types/ofParameterGroup.h; sourceTree = SOURCE_ROOT; }; + E42962DC2163EDD300A6A9E2 /* ofColor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofColor.h; path = ../../../libs/openFrameworks/types/ofColor.h; sourceTree = SOURCE_ROOT; }; + E42962DD2163EDD300A6A9E2 /* ofRectangle.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofRectangle.cpp; path = ../../../libs/openFrameworks/types/ofRectangle.cpp; sourceTree = SOURCE_ROOT; }; + E42962DE2163EDD300A6A9E2 /* ofPoint.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofPoint.h; path = ../../../libs/openFrameworks/types/ofPoint.h; sourceTree = SOURCE_ROOT; }; + E42962DF2163EDD300A6A9E2 /* ofTypes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofTypes.h; path = ../../../libs/openFrameworks/types/ofTypes.h; sourceTree = SOURCE_ROOT; }; + E42962E02163EDD300A6A9E2 /* ofRectangle.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofRectangle.h; path = ../../../libs/openFrameworks/types/ofRectangle.h; sourceTree = SOURCE_ROOT; }; + E42962E12163EDD300A6A9E2 /* ofBaseTypes.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofBaseTypes.cpp; path = ../../../libs/openFrameworks/types/ofBaseTypes.cpp; sourceTree = SOURCE_ROOT; }; + E42962E22163EDD300A6A9E2 /* ofParameterGroup.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofParameterGroup.cpp; path = ../../../libs/openFrameworks/types/ofParameterGroup.cpp; sourceTree = SOURCE_ROOT; }; + E42962E32163EDD300A6A9E2 /* ofParameter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofParameter.h; path = ../../../libs/openFrameworks/types/ofParameter.h; sourceTree = SOURCE_ROOT; }; + E42962E42163EDD300A6A9E2 /* ofColor.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofColor.cpp; path = ../../../libs/openFrameworks/types/ofColor.cpp; sourceTree = SOURCE_ROOT; }; + E42962E52163EDD300A6A9E2 /* ofParameter.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofParameter.cpp; path = ../../../libs/openFrameworks/types/ofParameter.cpp; sourceTree = SOURCE_ROOT; }; + E42962E62163EDD300A6A9E2 /* ofBaseTypes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofBaseTypes.h; path = ../../../libs/openFrameworks/types/ofBaseTypes.h; sourceTree = SOURCE_ROOT; }; + E42962E82163EDD300A6A9E2 /* ofMainLoop.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofMainLoop.h; path = ../../../libs/openFrameworks/app/ofMainLoop.h; sourceTree = SOURCE_ROOT; }; + E42962E92163EDD300A6A9E2 /* ofBaseApp.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofBaseApp.cpp; path = ../../../libs/openFrameworks/app/ofBaseApp.cpp; sourceTree = SOURCE_ROOT; }; + E42962EA2163EDD300A6A9E2 /* ofAppGlutWindow.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofAppGlutWindow.cpp; path = ../../../libs/openFrameworks/app/ofAppGlutWindow.cpp; sourceTree = SOURCE_ROOT; }; + E42962EB2163EDD300A6A9E2 /* ofWindowSettings.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofWindowSettings.h; path = ../../../libs/openFrameworks/app/ofWindowSettings.h; sourceTree = SOURCE_ROOT; }; + E42962EC2163EDD300A6A9E2 /* ofAppEGLWindow.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofAppEGLWindow.cpp; path = ../../../libs/openFrameworks/app/ofAppEGLWindow.cpp; sourceTree = SOURCE_ROOT; }; + E42962ED2163EDD300A6A9E2 /* ofAppBaseWindow.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofAppBaseWindow.h; path = ../../../libs/openFrameworks/app/ofAppBaseWindow.h; sourceTree = SOURCE_ROOT; }; + E42962EE2163EDD300A6A9E2 /* ofIcon.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofIcon.h; path = ../../../libs/openFrameworks/app/ofIcon.h; sourceTree = SOURCE_ROOT; }; + E42962EF2163EDD300A6A9E2 /* ofAppNoWindow.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofAppNoWindow.h; path = ../../../libs/openFrameworks/app/ofAppNoWindow.h; sourceTree = SOURCE_ROOT; }; + E42962F02163EDD300A6A9E2 /* ofBaseApp.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofBaseApp.h; path = ../../../libs/openFrameworks/app/ofBaseApp.h; sourceTree = SOURCE_ROOT; }; + E42962F12163EDD300A6A9E2 /* ofAppEGLWindow.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofAppEGLWindow.h; path = ../../../libs/openFrameworks/app/ofAppEGLWindow.h; sourceTree = SOURCE_ROOT; }; + E42962F22163EDD300A6A9E2 /* ofMainLoop.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofMainLoop.cpp; path = ../../../libs/openFrameworks/app/ofMainLoop.cpp; sourceTree = SOURCE_ROOT; }; + E42962F32163EDD300A6A9E2 /* ofAppRunner.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofAppRunner.h; path = ../../../libs/openFrameworks/app/ofAppRunner.h; sourceTree = SOURCE_ROOT; }; + E42962F42163EDD300A6A9E2 /* ofAppNoWindow.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofAppNoWindow.cpp; path = ../../../libs/openFrameworks/app/ofAppNoWindow.cpp; sourceTree = SOURCE_ROOT; }; + E42962F52163EDD300A6A9E2 /* ofAppGLFWWindow.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofAppGLFWWindow.cpp; path = ../../../libs/openFrameworks/app/ofAppGLFWWindow.cpp; sourceTree = SOURCE_ROOT; }; + E42962F62163EDD300A6A9E2 /* ofAppGlutWindow.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofAppGlutWindow.h; path = ../../../libs/openFrameworks/app/ofAppGlutWindow.h; sourceTree = SOURCE_ROOT; }; + E42962F72163EDD300A6A9E2 /* ofAppRunner.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofAppRunner.cpp; path = ../../../libs/openFrameworks/app/ofAppRunner.cpp; sourceTree = SOURCE_ROOT; }; + E42962F82163EDD300A6A9E2 /* ofAppGLFWWindow.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofAppGLFWWindow.h; path = ../../../libs/openFrameworks/app/ofAppGLFWWindow.h; sourceTree = SOURCE_ROOT; }; + E42962F92163EDD300A6A9E2 /* ofMain.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofMain.h; path = ../../../libs/openFrameworks/ofMain.h; sourceTree = SOURCE_ROOT; }; + E42962FB2163EDD300A6A9E2 /* ofThreadChannel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofThreadChannel.h; path = ../../../libs/openFrameworks/utils/ofThreadChannel.h; sourceTree = SOURCE_ROOT; }; + E42962FC2163EDD300A6A9E2 /* ofThread.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofThread.cpp; path = ../../../libs/openFrameworks/utils/ofThread.cpp; sourceTree = SOURCE_ROOT; }; + E42962FD2163EDD300A6A9E2 /* ofFpsCounter.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofFpsCounter.cpp; path = ../../../libs/openFrameworks/utils/ofFpsCounter.cpp; sourceTree = SOURCE_ROOT; }; + E42962FE2163EDD300A6A9E2 /* ofURLFileLoader.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofURLFileLoader.cpp; path = ../../../libs/openFrameworks/utils/ofURLFileLoader.cpp; sourceTree = SOURCE_ROOT; }; + E42962FF2163EDD300A6A9E2 /* ofConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofConstants.h; path = ../../../libs/openFrameworks/utils/ofConstants.h; sourceTree = SOURCE_ROOT; }; + E42963002163EDD300A6A9E2 /* ofXml.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofXml.h; path = ../../../libs/openFrameworks/utils/ofXml.h; sourceTree = SOURCE_ROOT; }; + E42963012163EDD300A6A9E2 /* ofFpsCounter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofFpsCounter.h; path = ../../../libs/openFrameworks/utils/ofFpsCounter.h; sourceTree = SOURCE_ROOT; }; + E42963022163EDD300A6A9E2 /* ofLog.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofLog.h; path = ../../../libs/openFrameworks/utils/ofLog.h; sourceTree = SOURCE_ROOT; }; + E42963032163EDD300A6A9E2 /* ofSystemUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofSystemUtils.h; path = ../../../libs/openFrameworks/utils/ofSystemUtils.h; sourceTree = SOURCE_ROOT; }; + E42963042163EDD300A6A9E2 /* ofSystemUtils.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofSystemUtils.cpp; path = ../../../libs/openFrameworks/utils/ofSystemUtils.cpp; sourceTree = SOURCE_ROOT; }; + E42963052163EDD300A6A9E2 /* ofUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofUtils.h; path = ../../../libs/openFrameworks/utils/ofUtils.h; sourceTree = SOURCE_ROOT; }; + E42963062163EDD300A6A9E2 /* ofNoise.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofNoise.h; path = ../../../libs/openFrameworks/utils/ofNoise.h; sourceTree = SOURCE_ROOT; }; + E42963072163EDD300A6A9E2 /* ofLog.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofLog.cpp; path = ../../../libs/openFrameworks/utils/ofLog.cpp; sourceTree = SOURCE_ROOT; }; + E42963082163EDD300A6A9E2 /* ofTimer.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofTimer.cpp; path = ../../../libs/openFrameworks/utils/ofTimer.cpp; sourceTree = SOURCE_ROOT; }; + E42963092163EDD300A6A9E2 /* ofFileUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofFileUtils.h; path = ../../../libs/openFrameworks/utils/ofFileUtils.h; sourceTree = SOURCE_ROOT; }; + E429630A2163EDD300A6A9E2 /* ofThread.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofThread.h; path = ../../../libs/openFrameworks/utils/ofThread.h; sourceTree = SOURCE_ROOT; }; + E429630B2163EDD300A6A9E2 /* ofURLFileLoader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofURLFileLoader.h; path = ../../../libs/openFrameworks/utils/ofURLFileLoader.h; sourceTree = SOURCE_ROOT; }; + E429630C2163EDD300A6A9E2 /* ofJson.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofJson.h; path = ../../../libs/openFrameworks/utils/ofJson.h; sourceTree = SOURCE_ROOT; }; + E429630D2163EDD300A6A9E2 /* ofFileUtils.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofFileUtils.cpp; path = ../../../libs/openFrameworks/utils/ofFileUtils.cpp; sourceTree = SOURCE_ROOT; }; + E429630E2163EDD300A6A9E2 /* ofMatrixStack.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofMatrixStack.cpp; path = ../../../libs/openFrameworks/utils/ofMatrixStack.cpp; sourceTree = SOURCE_ROOT; }; + E429630F2163EDD300A6A9E2 /* ofUtils.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofUtils.cpp; path = ../../../libs/openFrameworks/utils/ofUtils.cpp; sourceTree = SOURCE_ROOT; }; + E42963102163EDD300A6A9E2 /* ofXml.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofXml.cpp; path = ../../../libs/openFrameworks/utils/ofXml.cpp; sourceTree = SOURCE_ROOT; }; + E42963112163EDD300A6A9E2 /* ofMatrixStack.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofMatrixStack.h; path = ../../../libs/openFrameworks/utils/ofMatrixStack.h; sourceTree = SOURCE_ROOT; }; + E42963122163EDD300A6A9E2 /* ofTimer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofTimer.h; path = ../../../libs/openFrameworks/utils/ofTimer.h; sourceTree = SOURCE_ROOT; }; + E42963142163EDD300A6A9E2 /* ofVec2f.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofVec2f.cpp; path = ../../../libs/openFrameworks/math/ofVec2f.cpp; sourceTree = SOURCE_ROOT; }; + E42963152163EDD300A6A9E2 /* ofVec4f.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofVec4f.cpp; path = ../../../libs/openFrameworks/math/ofVec4f.cpp; sourceTree = SOURCE_ROOT; }; + E42963162163EDD300A6A9E2 /* ofVec4f.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofVec4f.h; path = ../../../libs/openFrameworks/math/ofVec4f.h; sourceTree = SOURCE_ROOT; }; + E42963172163EDD300A6A9E2 /* ofMath.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofMath.cpp; path = ../../../libs/openFrameworks/math/ofMath.cpp; sourceTree = SOURCE_ROOT; }; + E42963182163EDD300A6A9E2 /* ofMathConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofMathConstants.h; path = ../../../libs/openFrameworks/math/ofMathConstants.h; sourceTree = SOURCE_ROOT; }; + E42963192163EDD300A6A9E2 /* ofMatrix4x4.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofMatrix4x4.h; path = ../../../libs/openFrameworks/math/ofMatrix4x4.h; sourceTree = SOURCE_ROOT; }; + E429631A2163EDD300A6A9E2 /* ofVec3f.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofVec3f.h; path = ../../../libs/openFrameworks/math/ofVec3f.h; sourceTree = SOURCE_ROOT; }; + E429631B2163EDD300A6A9E2 /* ofMatrix3x3.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofMatrix3x3.cpp; path = ../../../libs/openFrameworks/math/ofMatrix3x3.cpp; sourceTree = SOURCE_ROOT; }; + E429631C2163EDD300A6A9E2 /* ofMath.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofMath.h; path = ../../../libs/openFrameworks/math/ofMath.h; sourceTree = SOURCE_ROOT; }; + E429631D2163EDD300A6A9E2 /* ofQuaternion.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofQuaternion.h; path = ../../../libs/openFrameworks/math/ofQuaternion.h; sourceTree = SOURCE_ROOT; }; + E429631E2163EDD300A6A9E2 /* ofVectorMath.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofVectorMath.h; path = ../../../libs/openFrameworks/math/ofVectorMath.h; sourceTree = SOURCE_ROOT; }; + E429631F2163EDD300A6A9E2 /* ofQuaternion.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofQuaternion.cpp; path = ../../../libs/openFrameworks/math/ofQuaternion.cpp; sourceTree = SOURCE_ROOT; }; + E42963202163EDD300A6A9E2 /* ofVec2f.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofVec2f.h; path = ../../../libs/openFrameworks/math/ofVec2f.h; sourceTree = SOURCE_ROOT; }; + E42963212163EDD300A6A9E2 /* ofMatrix4x4.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofMatrix4x4.cpp; path = ../../../libs/openFrameworks/math/ofMatrix4x4.cpp; sourceTree = SOURCE_ROOT; }; + E42963222163EDD300A6A9E2 /* ofMatrix3x3.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofMatrix3x3.h; path = ../../../libs/openFrameworks/math/ofMatrix3x3.h; sourceTree = SOURCE_ROOT; }; + E42963262163EDD300A6A9E2 /* ofVboMesh.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofVboMesh.h; path = ../../../libs/openFrameworks/gl/ofVboMesh.h; sourceTree = SOURCE_ROOT; }; + E42963272163EDD300A6A9E2 /* ofTexture.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofTexture.h; path = ../../../libs/openFrameworks/gl/ofTexture.h; sourceTree = SOURCE_ROOT; }; + E42963282163EDD300A6A9E2 /* ofShader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofShader.h; path = ../../../libs/openFrameworks/gl/ofShader.h; sourceTree = SOURCE_ROOT; }; + E42963292163EDD300A6A9E2 /* ofMaterial.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofMaterial.cpp; path = ../../../libs/openFrameworks/gl/ofMaterial.cpp; sourceTree = SOURCE_ROOT; }; + E429632A2163EDD300A6A9E2 /* ofFbo.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofFbo.cpp; path = ../../../libs/openFrameworks/gl/ofFbo.cpp; sourceTree = SOURCE_ROOT; }; + E429632B2163EDD300A6A9E2 /* ofLight.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofLight.h; path = ../../../libs/openFrameworks/gl/ofLight.h; sourceTree = SOURCE_ROOT; }; + E429632C2163EDD300A6A9E2 /* ofMaterial.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofMaterial.h; path = ../../../libs/openFrameworks/gl/ofMaterial.h; sourceTree = SOURCE_ROOT; }; + E429632D2163EDD300A6A9E2 /* ofShader.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofShader.cpp; path = ../../../libs/openFrameworks/gl/ofShader.cpp; sourceTree = SOURCE_ROOT; }; + E429632E2163EDD300A6A9E2 /* ofBufferObject.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofBufferObject.cpp; path = ../../../libs/openFrameworks/gl/ofBufferObject.cpp; sourceTree = SOURCE_ROOT; }; + E429632F2163EDD300A6A9E2 /* ofVboMesh.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofVboMesh.cpp; path = ../../../libs/openFrameworks/gl/ofVboMesh.cpp; sourceTree = SOURCE_ROOT; }; + E42963302163EDD300A6A9E2 /* ofGLUtils.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofGLUtils.cpp; path = ../../../libs/openFrameworks/gl/ofGLUtils.cpp; sourceTree = SOURCE_ROOT; }; + E42963312163EDD300A6A9E2 /* ofVbo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofVbo.h; path = ../../../libs/openFrameworks/gl/ofVbo.h; sourceTree = SOURCE_ROOT; }; + E42963322163EDD300A6A9E2 /* ofLight.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofLight.cpp; path = ../../../libs/openFrameworks/gl/ofLight.cpp; sourceTree = SOURCE_ROOT; }; + E42963332163EDD300A6A9E2 /* ofBufferObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofBufferObject.h; path = ../../../libs/openFrameworks/gl/ofBufferObject.h; sourceTree = SOURCE_ROOT; }; + E42963342163EDD300A6A9E2 /* ofGLProgrammableRenderer.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofGLProgrammableRenderer.cpp; path = ../../../libs/openFrameworks/gl/ofGLProgrammableRenderer.cpp; sourceTree = SOURCE_ROOT; }; + E42963382163EDD300A6A9E2 /* ofFbo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofFbo.h; path = ../../../libs/openFrameworks/gl/ofFbo.h; sourceTree = SOURCE_ROOT; }; + E42963392163EDD300A6A9E2 /* ofGLBaseTypes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofGLBaseTypes.h; path = ../../../libs/openFrameworks/gl/ofGLBaseTypes.h; sourceTree = SOURCE_ROOT; }; + E429633A2163EDD300A6A9E2 /* ofVbo.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofVbo.cpp; path = ../../../libs/openFrameworks/gl/ofVbo.cpp; sourceTree = SOURCE_ROOT; }; + E429633B2163EDD300A6A9E2 /* ofGLProgrammableRenderer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofGLProgrammableRenderer.h; path = ../../../libs/openFrameworks/gl/ofGLProgrammableRenderer.h; sourceTree = SOURCE_ROOT; }; + E429633C2163EDD300A6A9E2 /* ofTexture.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofTexture.cpp; path = ../../../libs/openFrameworks/gl/ofTexture.cpp; sourceTree = SOURCE_ROOT; }; + E429633D2163EDD300A6A9E2 /* ofGLUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofGLUtils.h; path = ../../../libs/openFrameworks/gl/ofGLUtils.h; sourceTree = SOURCE_ROOT; }; + E429633E2163EDD300A6A9E2 /* ofGLRenderer.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofGLRenderer.cpp; path = ../../../libs/openFrameworks/gl/ofGLRenderer.cpp; sourceTree = SOURCE_ROOT; }; + E429633F2163EDD300A6A9E2 /* ofGLRenderer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofGLRenderer.h; path = ../../../libs/openFrameworks/gl/ofGLRenderer.h; sourceTree = SOURCE_ROOT; }; + E42963412163EDD300A6A9E2 /* ofArduino.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofArduino.h; path = ../../../libs/openFrameworks/communication/ofArduino.h; sourceTree = SOURCE_ROOT; }; + E42963422163EDD300A6A9E2 /* ofSerial.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofSerial.cpp; path = ../../../libs/openFrameworks/communication/ofSerial.cpp; sourceTree = SOURCE_ROOT; }; + E42963432163EDD300A6A9E2 /* ofSerial.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofSerial.h; path = ../../../libs/openFrameworks/communication/ofSerial.h; sourceTree = SOURCE_ROOT; }; + E42963442163EDD300A6A9E2 /* ofArduino.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofArduino.cpp; path = ../../../libs/openFrameworks/communication/ofArduino.cpp; sourceTree = SOURCE_ROOT; }; + E42963462163EDD300A6A9E2 /* ofEventUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofEventUtils.h; path = ../../../libs/openFrameworks/events/ofEventUtils.h; sourceTree = SOURCE_ROOT; }; + E42963472163EDD300A6A9E2 /* ofEvent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofEvent.h; path = ../../../libs/openFrameworks/events/ofEvent.h; sourceTree = SOURCE_ROOT; }; + E42963482163EDD300A6A9E2 /* ofEvents.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofEvents.h; path = ../../../libs/openFrameworks/events/ofEvents.h; sourceTree = SOURCE_ROOT; }; + E42963492163EDD300A6A9E2 /* ofEvents.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofEvents.cpp; path = ../../../libs/openFrameworks/events/ofEvents.cpp; sourceTree = SOURCE_ROOT; }; + E429634B2163EDD300A6A9E2 /* ofGraphicsBaseTypes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofGraphicsBaseTypes.h; path = ../../../libs/openFrameworks/graphics/ofGraphicsBaseTypes.h; sourceTree = SOURCE_ROOT; }; + E429634C2163EDD300A6A9E2 /* ofPath.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofPath.h; path = ../../../libs/openFrameworks/graphics/ofPath.h; sourceTree = SOURCE_ROOT; }; + E429634D2163EDD300A6A9E2 /* ofBitmapFont.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofBitmapFont.cpp; path = ../../../libs/openFrameworks/graphics/ofBitmapFont.cpp; sourceTree = SOURCE_ROOT; }; + E429634E2163EDD300A6A9E2 /* ofTrueTypeFont.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofTrueTypeFont.h; path = ../../../libs/openFrameworks/graphics/ofTrueTypeFont.h; sourceTree = SOURCE_ROOT; }; + E429634F2163EDD300A6A9E2 /* ofTessellator.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofTessellator.cpp; path = ../../../libs/openFrameworks/graphics/ofTessellator.cpp; sourceTree = SOURCE_ROOT; }; + E42963502163EDD300A6A9E2 /* ofGraphics.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofGraphics.cpp; path = ../../../libs/openFrameworks/graphics/ofGraphics.cpp; sourceTree = SOURCE_ROOT; }; + E42963512163EDD300A6A9E2 /* ofPolyline.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofPolyline.h; path = ../../../libs/openFrameworks/graphics/ofPolyline.h; sourceTree = SOURCE_ROOT; }; + E42963522163EDD300A6A9E2 /* ofGraphicsBaseTypes.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofGraphicsBaseTypes.cpp; path = ../../../libs/openFrameworks/graphics/ofGraphicsBaseTypes.cpp; sourceTree = SOURCE_ROOT; }; + E42963532163EDD300A6A9E2 /* ofCairoRenderer.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofCairoRenderer.cpp; path = ../../../libs/openFrameworks/graphics/ofCairoRenderer.cpp; sourceTree = SOURCE_ROOT; }; + E42963542163EDD300A6A9E2 /* ofRendererCollection.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofRendererCollection.cpp; path = ../../../libs/openFrameworks/graphics/ofRendererCollection.cpp; sourceTree = SOURCE_ROOT; }; + E42963552163EDD300A6A9E2 /* ofPath.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofPath.cpp; path = ../../../libs/openFrameworks/graphics/ofPath.cpp; sourceTree = SOURCE_ROOT; }; + E42963562163EDD300A6A9E2 /* ofPixels.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofPixels.cpp; path = ../../../libs/openFrameworks/graphics/ofPixels.cpp; sourceTree = SOURCE_ROOT; }; + E42963572163EDD300A6A9E2 /* ofPolyline.inl */ = {isa = PBXFileReference; lastKnownFileType = text; name = ofPolyline.inl; path = ../../../libs/openFrameworks/graphics/ofPolyline.inl; sourceTree = SOURCE_ROOT; }; + E42963582163EDD300A6A9E2 /* of3dGraphics.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = of3dGraphics.cpp; path = ../../../libs/openFrameworks/graphics/of3dGraphics.cpp; sourceTree = SOURCE_ROOT; }; + E42963592163EDD300A6A9E2 /* ofImage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofImage.h; path = ../../../libs/openFrameworks/graphics/ofImage.h; sourceTree = SOURCE_ROOT; }; + E429635A2163EDD300A6A9E2 /* ofPixels.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofPixels.h; path = ../../../libs/openFrameworks/graphics/ofPixels.h; sourceTree = SOURCE_ROOT; }; + E429635B2163EDD300A6A9E2 /* ofCairoRenderer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofCairoRenderer.h; path = ../../../libs/openFrameworks/graphics/ofCairoRenderer.h; sourceTree = SOURCE_ROOT; }; + E429635C2163EDD300A6A9E2 /* ofBitmapFont.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofBitmapFont.h; path = ../../../libs/openFrameworks/graphics/ofBitmapFont.h; sourceTree = SOURCE_ROOT; }; + E429635D2163EDD300A6A9E2 /* ofTrueTypeFont.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofTrueTypeFont.cpp; path = ../../../libs/openFrameworks/graphics/ofTrueTypeFont.cpp; sourceTree = SOURCE_ROOT; }; + E429635E2163EDD300A6A9E2 /* ofRendererCollection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofRendererCollection.h; path = ../../../libs/openFrameworks/graphics/ofRendererCollection.h; sourceTree = SOURCE_ROOT; }; + E429635F2163EDD300A6A9E2 /* ofGraphics.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofGraphics.h; path = ../../../libs/openFrameworks/graphics/ofGraphics.h; sourceTree = SOURCE_ROOT; }; + E42963602163EDD300A6A9E2 /* ofGraphicsConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofGraphicsConstants.h; path = ../../../libs/openFrameworks/graphics/ofGraphicsConstants.h; sourceTree = SOURCE_ROOT; }; + E42963612163EDD300A6A9E2 /* ofImage.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofImage.cpp; path = ../../../libs/openFrameworks/graphics/ofImage.cpp; sourceTree = SOURCE_ROOT; }; + E42963622163EDD300A6A9E2 /* of3dGraphics.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = of3dGraphics.h; path = ../../../libs/openFrameworks/graphics/of3dGraphics.h; sourceTree = SOURCE_ROOT; }; + E42963632163EDD300A6A9E2 /* ofTessellator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofTessellator.h; path = ../../../libs/openFrameworks/graphics/ofTessellator.h; sourceTree = SOURCE_ROOT; }; + E42963652163EDD300A6A9E2 /* ofSoundStream.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofSoundStream.cpp; path = ../../../libs/openFrameworks/sound/ofSoundStream.cpp; sourceTree = SOURCE_ROOT; }; + E42963662163EDD300A6A9E2 /* ofSoundStream.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofSoundStream.h; path = ../../../libs/openFrameworks/sound/ofSoundStream.h; sourceTree = SOURCE_ROOT; }; + E42963672163EDD300A6A9E2 /* ofSoundPlayer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofSoundPlayer.h; path = ../../../libs/openFrameworks/sound/ofSoundPlayer.h; sourceTree = SOURCE_ROOT; }; + E42963682163EDD300A6A9E2 /* ofOpenALSoundPlayer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofOpenALSoundPlayer.h; path = ../../../libs/openFrameworks/sound/ofOpenALSoundPlayer.h; sourceTree = SOURCE_ROOT; }; + E42963692163EDD300A6A9E2 /* ofRtAudioSoundStream.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofRtAudioSoundStream.cpp; path = ../../../libs/openFrameworks/sound/ofRtAudioSoundStream.cpp; sourceTree = SOURCE_ROOT; }; + E429636A2163EDD300A6A9E2 /* ofFmodSoundPlayer.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofFmodSoundPlayer.cpp; path = ../../../libs/openFrameworks/sound/ofFmodSoundPlayer.cpp; sourceTree = SOURCE_ROOT; }; + E429636B2163EDD300A6A9E2 /* ofSoundBaseTypes.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofSoundBaseTypes.cpp; path = ../../../libs/openFrameworks/sound/ofSoundBaseTypes.cpp; sourceTree = SOURCE_ROOT; }; + E429636C2163EDD300A6A9E2 /* ofOpenALSoundPlayer.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofOpenALSoundPlayer.cpp; path = ../../../libs/openFrameworks/sound/ofOpenALSoundPlayer.cpp; sourceTree = SOURCE_ROOT; }; + E429636D2163EDD300A6A9E2 /* ofSoundPlayer.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofSoundPlayer.cpp; path = ../../../libs/openFrameworks/sound/ofSoundPlayer.cpp; sourceTree = SOURCE_ROOT; }; + E429636E2163EDD300A6A9E2 /* ofRtAudioSoundStream.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofRtAudioSoundStream.h; path = ../../../libs/openFrameworks/sound/ofRtAudioSoundStream.h; sourceTree = SOURCE_ROOT; }; + E429636F2163EDD300A6A9E2 /* ofSoundUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofSoundUtils.h; path = ../../../libs/openFrameworks/sound/ofSoundUtils.h; sourceTree = SOURCE_ROOT; }; + E42963702163EDD300A6A9E2 /* ofSoundBuffer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofSoundBuffer.h; path = ../../../libs/openFrameworks/sound/ofSoundBuffer.h; sourceTree = SOURCE_ROOT; }; + E42963712163EDD300A6A9E2 /* ofFmodSoundPlayer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofFmodSoundPlayer.h; path = ../../../libs/openFrameworks/sound/ofFmodSoundPlayer.h; sourceTree = SOURCE_ROOT; }; + E42963722163EDD300A6A9E2 /* ofSoundBuffer.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ofSoundBuffer.cpp; path = ../../../libs/openFrameworks/sound/ofSoundBuffer.cpp; sourceTree = SOURCE_ROOT; }; + E42963732163EDD300A6A9E2 /* ofSoundBaseTypes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofSoundBaseTypes.h; path = ../../../libs/openFrameworks/sound/ofSoundBaseTypes.h; sourceTree = SOURCE_ROOT; }; + E4B69B5B0A3A1756003C02F2 /* EmotiBitSlidePlayerDebug.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = EmotiBitSlidePlayerDebug.app; sourceTree = BUILT_PRODUCTS_DIR; }; + E4B69E1D0A3A1BDC003C02F2 /* main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = main.cpp; path = src/main.cpp; sourceTree = SOURCE_ROOT; }; + E4B69E1E0A3A1BDC003C02F2 /* ofApp.cpp */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.cpp; fileEncoding = 4; name = ofApp.cpp; path = src/ofApp.cpp; sourceTree = SOURCE_ROOT; }; + E4B69E1F0A3A1BDC003C02F2 /* ofApp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ofApp.h; path = src/ofApp.h; sourceTree = SOURCE_ROOT; }; + E4B6FCAD0C3E899E008CF71C /* openFrameworks-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "openFrameworks-Info.plist"; sourceTree = ""; }; + E4EB6923138AFD0F00A09F29 /* Project.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Project.xcconfig; sourceTree = ""; }; + F2B099E6BD1199664C48B177 /* ofxJSONElement.cpp */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.cpp; fileEncoding = 4; name = ofxJSONElement.cpp; path = ../../../addons/ofxJSON/src/ofxJSONElement.cpp; sourceTree = SOURCE_ROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + E4B69B590A3A1756003C02F2 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 2865DAEF86B1907A704CA70B /* ofxJSON */ = { + isa = PBXGroup; + children = ( + F40E80CB2D443CBA9581DD03 /* libs */, + 292AF6148769654D0DF26018 /* src */, + ); + name = ofxJSON; + sourceTree = ""; + }; + 292AF6148769654D0DF26018 /* src */ = { + isa = PBXGroup; + children = ( + 1645F56257269CD0356320BD /* ofxJSON.h */, + F2B099E6BD1199664C48B177 /* ofxJSONElement.cpp */, + 26A541233BC6F736E758F718 /* ofxJSONElement.h */, + ); + name = src; + sourceTree = ""; + }; + 58AD3BD71B781D9BC25763C8 /* json */ = { + isa = PBXGroup; + children = ( + 61313493CDB52744E22A604D /* json-forwards.h */, + 2C7CF000B7B4F782C187C353 /* json.h */, + ); + name = json; + sourceTree = ""; + }; + 6948EE371B920CB800B5AC1A /* local_addons */ = { + isa = PBXGroup; + children = ( + ); + name = local_addons; + sourceTree = ""; + }; + 977A836DD2C489CCC5E330FF /* jsoncpp */ = { + isa = PBXGroup; + children = ( + D486FC87F063317BB47E9FAC /* include */, + CCDC6F9CCF925CC402160B85 /* src */, + ); + name = jsoncpp; + sourceTree = ""; + }; + BB4B014C10F69532006C3DED /* addons */ = { + isa = PBXGroup; + children = ( + 2865DAEF86B1907A704CA70B /* ofxJSON */, + ); + name = addons; + sourceTree = ""; + }; + CCDC6F9CCF925CC402160B85 /* src */ = { + isa = PBXGroup; + children = ( + 21BDE665988474F1B1F4D302 /* jsoncpp.cpp */, + ); + name = src; + sourceTree = ""; + }; + D486FC87F063317BB47E9FAC /* include */ = { + isa = PBXGroup; + children = ( + 58AD3BD71B781D9BC25763C8 /* json */, + ); + name = include; + sourceTree = ""; + }; + E42962AA2163EDD300A6A9E2 /* openFrameworks */ = { + isa = PBXGroup; + children = ( + E42962AB2163EDD300A6A9E2 /* 3d */, + E42962E72163EDD300A6A9E2 /* app */, + E42963402163EDD300A6A9E2 /* communication */, + E42963452163EDD300A6A9E2 /* events */, + E42963252163EDD300A6A9E2 /* gl */, + E429634A2163EDD300A6A9E2 /* graphics */, + E42963132163EDD300A6A9E2 /* math */, + E42962F92163EDD300A6A9E2 /* ofMain.h */, + E42963642163EDD300A6A9E2 /* sound */, + E42962DA2163EDD300A6A9E2 /* types */, + E42962FA2163EDD300A6A9E2 /* utils */, + E42962B82163EDD300A6A9E2 /* video */, + ); + name = openFrameworks; + path = ../../../libs/openFrameworks; + sourceTree = ""; + }; + E42962AB2163EDD300A6A9E2 /* 3d */ = { + isa = PBXGroup; + children = ( + E42962B02163EDD300A6A9E2 /* of3dPrimitives.cpp */, + E42962B62163EDD300A6A9E2 /* of3dPrimitives.h */, + E42962B52163EDD300A6A9E2 /* of3dUtils.cpp */, + E42962B12163EDD300A6A9E2 /* of3dUtils.h */, + E42962AC2163EDD300A6A9E2 /* ofCamera.cpp */, + E42962B32163EDD300A6A9E2 /* ofCamera.h */, + E42962B22163EDD300A6A9E2 /* ofEasyCam.cpp */, + E42962B72163EDD300A6A9E2 /* ofEasyCam.h */, + E42962AD2163EDD300A6A9E2 /* ofMesh.h */, + E42962B42163EDD300A6A9E2 /* ofMesh.inl */, + E42962AF2163EDD300A6A9E2 /* ofNode.cpp */, + E42962AE2163EDD300A6A9E2 /* ofNode.h */, + ); + name = 3d; + path = ../../../libs/openFrameworks/3d; + sourceTree = SOURCE_ROOT; + }; + E42962B82163EDD300A6A9E2 /* video */ = { + isa = PBXGroup; + children = ( + E42962BA2163EDD300A6A9E2 /* ofAVFoundationGrabber.h */, + E42962C22163EDD300A6A9E2 /* ofAVFoundationGrabber.mm */, + E42962BC2163EDD300A6A9E2 /* ofAVFoundationPlayer.h */, + E42962CE2163EDD300A6A9E2 /* ofAVFoundationPlayer.mm */, + E42962C12163EDD300A6A9E2 /* ofAVFoundationVideoPlayer.h */, + E42962D72163EDD300A6A9E2 /* ofAVFoundationVideoPlayer.m */, + E42962C52163EDD300A6A9E2 /* ofQTKitGrabber.h */, + E42962BE2163EDD300A6A9E2 /* ofQTKitGrabber.mm */, + E42962C32163EDD300A6A9E2 /* ofQTKitMovieRenderer.h */, + E42962D62163EDD300A6A9E2 /* ofQTKitMovieRenderer.m */, + E42962CB2163EDD300A6A9E2 /* ofQTKitPlayer.h */, + E42962D52163EDD300A6A9E2 /* ofQTKitPlayer.mm */, + E42962BD2163EDD300A6A9E2 /* ofQtUtils.cpp */, + E42962CD2163EDD300A6A9E2 /* ofQtUtils.h */, + E42962BF2163EDD300A6A9E2 /* ofQuickTimeGrabber.cpp */, + E42962C72163EDD300A6A9E2 /* ofQuickTimeGrabber.h */, + E42962C92163EDD300A6A9E2 /* ofQuickTimePlayer.cpp */, + E42962CF2163EDD300A6A9E2 /* ofQuickTimePlayer.h */, + E42962D92163EDD300A6A9E2 /* ofVideoBaseTypes.h */, + E42962D02163EDD300A6A9E2 /* ofVideoGrabber.cpp */, + E42962CA2163EDD300A6A9E2 /* ofVideoGrabber.h */, + E42962C42163EDD300A6A9E2 /* ofVideoPlayer.cpp */, + E42962BB2163EDD300A6A9E2 /* ofVideoPlayer.h */, + ); + name = video; + path = ../../../libs/openFrameworks/video; + sourceTree = SOURCE_ROOT; + }; + E42962DA2163EDD300A6A9E2 /* types */ = { + isa = PBXGroup; + children = ( + E42962E12163EDD300A6A9E2 /* ofBaseTypes.cpp */, + E42962E62163EDD300A6A9E2 /* ofBaseTypes.h */, + E42962E42163EDD300A6A9E2 /* ofColor.cpp */, + E42962DC2163EDD300A6A9E2 /* ofColor.h */, + E42962E52163EDD300A6A9E2 /* ofParameter.cpp */, + E42962E32163EDD300A6A9E2 /* ofParameter.h */, + E42962E22163EDD300A6A9E2 /* ofParameterGroup.cpp */, + E42962DB2163EDD300A6A9E2 /* ofParameterGroup.h */, + E42962DE2163EDD300A6A9E2 /* ofPoint.h */, + E42962DD2163EDD300A6A9E2 /* ofRectangle.cpp */, + E42962E02163EDD300A6A9E2 /* ofRectangle.h */, + E42962DF2163EDD300A6A9E2 /* ofTypes.h */, + ); + name = types; + path = ../../../libs/openFrameworks/types; + sourceTree = SOURCE_ROOT; + }; + E42962E72163EDD300A6A9E2 /* app */ = { + isa = PBXGroup; + children = ( + E42962ED2163EDD300A6A9E2 /* ofAppBaseWindow.h */, + E42962EC2163EDD300A6A9E2 /* ofAppEGLWindow.cpp */, + E42962F12163EDD300A6A9E2 /* ofAppEGLWindow.h */, + E42962F52163EDD300A6A9E2 /* ofAppGLFWWindow.cpp */, + E42962F82163EDD300A6A9E2 /* ofAppGLFWWindow.h */, + E42962EA2163EDD300A6A9E2 /* ofAppGlutWindow.cpp */, + E42962F62163EDD300A6A9E2 /* ofAppGlutWindow.h */, + E42962F42163EDD300A6A9E2 /* ofAppNoWindow.cpp */, + E42962EF2163EDD300A6A9E2 /* ofAppNoWindow.h */, + E42962F72163EDD300A6A9E2 /* ofAppRunner.cpp */, + E42962F32163EDD300A6A9E2 /* ofAppRunner.h */, + E42962E92163EDD300A6A9E2 /* ofBaseApp.cpp */, + E42962F02163EDD300A6A9E2 /* ofBaseApp.h */, + E42962EE2163EDD300A6A9E2 /* ofIcon.h */, + E42962F22163EDD300A6A9E2 /* ofMainLoop.cpp */, + E42962E82163EDD300A6A9E2 /* ofMainLoop.h */, + E42962EB2163EDD300A6A9E2 /* ofWindowSettings.h */, + ); + name = app; + path = ../../../libs/openFrameworks/app; + sourceTree = SOURCE_ROOT; + }; + E42962FA2163EDD300A6A9E2 /* utils */ = { + isa = PBXGroup; + children = ( + E42962FF2163EDD300A6A9E2 /* ofConstants.h */, + E429630D2163EDD300A6A9E2 /* ofFileUtils.cpp */, + E42963092163EDD300A6A9E2 /* ofFileUtils.h */, + E42962FD2163EDD300A6A9E2 /* ofFpsCounter.cpp */, + E42963012163EDD300A6A9E2 /* ofFpsCounter.h */, + E429630C2163EDD300A6A9E2 /* ofJson.h */, + E42963072163EDD300A6A9E2 /* ofLog.cpp */, + E42963022163EDD300A6A9E2 /* ofLog.h */, + E429630E2163EDD300A6A9E2 /* ofMatrixStack.cpp */, + E42963112163EDD300A6A9E2 /* ofMatrixStack.h */, + E42963062163EDD300A6A9E2 /* ofNoise.h */, + E42963042163EDD300A6A9E2 /* ofSystemUtils.cpp */, + E42963032163EDD300A6A9E2 /* ofSystemUtils.h */, + E42962FC2163EDD300A6A9E2 /* ofThread.cpp */, + E429630A2163EDD300A6A9E2 /* ofThread.h */, + E42962FB2163EDD300A6A9E2 /* ofThreadChannel.h */, + E42963082163EDD300A6A9E2 /* ofTimer.cpp */, + E42963122163EDD300A6A9E2 /* ofTimer.h */, + E42962FE2163EDD300A6A9E2 /* ofURLFileLoader.cpp */, + E429630B2163EDD300A6A9E2 /* ofURLFileLoader.h */, + E429630F2163EDD300A6A9E2 /* ofUtils.cpp */, + E42963052163EDD300A6A9E2 /* ofUtils.h */, + E42963102163EDD300A6A9E2 /* ofXml.cpp */, + E42963002163EDD300A6A9E2 /* ofXml.h */, + ); + name = utils; + path = ../../../libs/openFrameworks/utils; + sourceTree = SOURCE_ROOT; + }; + E42963132163EDD300A6A9E2 /* math */ = { + isa = PBXGroup; + children = ( + E42963172163EDD300A6A9E2 /* ofMath.cpp */, + E429631C2163EDD300A6A9E2 /* ofMath.h */, + E42963182163EDD300A6A9E2 /* ofMathConstants.h */, + E429631B2163EDD300A6A9E2 /* ofMatrix3x3.cpp */, + E42963222163EDD300A6A9E2 /* ofMatrix3x3.h */, + E42963212163EDD300A6A9E2 /* ofMatrix4x4.cpp */, + E42963192163EDD300A6A9E2 /* ofMatrix4x4.h */, + E429631F2163EDD300A6A9E2 /* ofQuaternion.cpp */, + E429631D2163EDD300A6A9E2 /* ofQuaternion.h */, + E42963142163EDD300A6A9E2 /* ofVec2f.cpp */, + E42963202163EDD300A6A9E2 /* ofVec2f.h */, + E429631A2163EDD300A6A9E2 /* ofVec3f.h */, + E42963152163EDD300A6A9E2 /* ofVec4f.cpp */, + E42963162163EDD300A6A9E2 /* ofVec4f.h */, + E429631E2163EDD300A6A9E2 /* ofVectorMath.h */, + ); + name = math; + path = ../../../libs/openFrameworks/math; + sourceTree = SOURCE_ROOT; + }; + E42963252163EDD300A6A9E2 /* gl */ = { + isa = PBXGroup; + children = ( + E429632E2163EDD300A6A9E2 /* ofBufferObject.cpp */, + E42963332163EDD300A6A9E2 /* ofBufferObject.h */, + E429632A2163EDD300A6A9E2 /* ofFbo.cpp */, + E42963382163EDD300A6A9E2 /* ofFbo.h */, + E42963392163EDD300A6A9E2 /* ofGLBaseTypes.h */, + E42963342163EDD300A6A9E2 /* ofGLProgrammableRenderer.cpp */, + E429633B2163EDD300A6A9E2 /* ofGLProgrammableRenderer.h */, + E429633E2163EDD300A6A9E2 /* ofGLRenderer.cpp */, + E429633F2163EDD300A6A9E2 /* ofGLRenderer.h */, + E42963302163EDD300A6A9E2 /* ofGLUtils.cpp */, + E429633D2163EDD300A6A9E2 /* ofGLUtils.h */, + E42963322163EDD300A6A9E2 /* ofLight.cpp */, + E429632B2163EDD300A6A9E2 /* ofLight.h */, + E42963292163EDD300A6A9E2 /* ofMaterial.cpp */, + E429632C2163EDD300A6A9E2 /* ofMaterial.h */, + E429632D2163EDD300A6A9E2 /* ofShader.cpp */, + E42963282163EDD300A6A9E2 /* ofShader.h */, + E429633C2163EDD300A6A9E2 /* ofTexture.cpp */, + E42963272163EDD300A6A9E2 /* ofTexture.h */, + E429633A2163EDD300A6A9E2 /* ofVbo.cpp */, + E42963312163EDD300A6A9E2 /* ofVbo.h */, + E429632F2163EDD300A6A9E2 /* ofVboMesh.cpp */, + E42963262163EDD300A6A9E2 /* ofVboMesh.h */, + ); + name = gl; + path = ../../../libs/openFrameworks/gl; + sourceTree = SOURCE_ROOT; + }; + E42963402163EDD300A6A9E2 /* communication */ = { + isa = PBXGroup; + children = ( + E42963442163EDD300A6A9E2 /* ofArduino.cpp */, + E42963412163EDD300A6A9E2 /* ofArduino.h */, + E42963422163EDD300A6A9E2 /* ofSerial.cpp */, + E42963432163EDD300A6A9E2 /* ofSerial.h */, + ); + name = communication; + path = ../../../libs/openFrameworks/communication; + sourceTree = SOURCE_ROOT; + }; + E42963452163EDD300A6A9E2 /* events */ = { + isa = PBXGroup; + children = ( + E42963472163EDD300A6A9E2 /* ofEvent.h */, + E42963492163EDD300A6A9E2 /* ofEvents.cpp */, + E42963482163EDD300A6A9E2 /* ofEvents.h */, + E42963462163EDD300A6A9E2 /* ofEventUtils.h */, + ); + name = events; + path = ../../../libs/openFrameworks/events; + sourceTree = SOURCE_ROOT; + }; + E429634A2163EDD300A6A9E2 /* graphics */ = { + isa = PBXGroup; + children = ( + E42963582163EDD300A6A9E2 /* of3dGraphics.cpp */, + E42963622163EDD300A6A9E2 /* of3dGraphics.h */, + E429634D2163EDD300A6A9E2 /* ofBitmapFont.cpp */, + E429635C2163EDD300A6A9E2 /* ofBitmapFont.h */, + E42963532163EDD300A6A9E2 /* ofCairoRenderer.cpp */, + E429635B2163EDD300A6A9E2 /* ofCairoRenderer.h */, + E42963502163EDD300A6A9E2 /* ofGraphics.cpp */, + E429635F2163EDD300A6A9E2 /* ofGraphics.h */, + E42963522163EDD300A6A9E2 /* ofGraphicsBaseTypes.cpp */, + E429634B2163EDD300A6A9E2 /* ofGraphicsBaseTypes.h */, + E42963602163EDD300A6A9E2 /* ofGraphicsConstants.h */, + E42963612163EDD300A6A9E2 /* ofImage.cpp */, + E42963592163EDD300A6A9E2 /* ofImage.h */, + E42963552163EDD300A6A9E2 /* ofPath.cpp */, + E429634C2163EDD300A6A9E2 /* ofPath.h */, + E42963562163EDD300A6A9E2 /* ofPixels.cpp */, + E429635A2163EDD300A6A9E2 /* ofPixels.h */, + E42963512163EDD300A6A9E2 /* ofPolyline.h */, + E42963572163EDD300A6A9E2 /* ofPolyline.inl */, + E42963542163EDD300A6A9E2 /* ofRendererCollection.cpp */, + E429635E2163EDD300A6A9E2 /* ofRendererCollection.h */, + E429634F2163EDD300A6A9E2 /* ofTessellator.cpp */, + E42963632163EDD300A6A9E2 /* ofTessellator.h */, + E429635D2163EDD300A6A9E2 /* ofTrueTypeFont.cpp */, + E429634E2163EDD300A6A9E2 /* ofTrueTypeFont.h */, + ); + name = graphics; + path = ../../../libs/openFrameworks/graphics; + sourceTree = SOURCE_ROOT; + }; + E42963642163EDD300A6A9E2 /* sound */ = { + isa = PBXGroup; + children = ( + E429636A2163EDD300A6A9E2 /* ofFmodSoundPlayer.cpp */, + E42963712163EDD300A6A9E2 /* ofFmodSoundPlayer.h */, + E429636C2163EDD300A6A9E2 /* ofOpenALSoundPlayer.cpp */, + E42963682163EDD300A6A9E2 /* ofOpenALSoundPlayer.h */, + E42963692163EDD300A6A9E2 /* ofRtAudioSoundStream.cpp */, + E429636E2163EDD300A6A9E2 /* ofRtAudioSoundStream.h */, + E429636B2163EDD300A6A9E2 /* ofSoundBaseTypes.cpp */, + E42963732163EDD300A6A9E2 /* ofSoundBaseTypes.h */, + E42963722163EDD300A6A9E2 /* ofSoundBuffer.cpp */, + E42963702163EDD300A6A9E2 /* ofSoundBuffer.h */, + E429636D2163EDD300A6A9E2 /* ofSoundPlayer.cpp */, + E42963672163EDD300A6A9E2 /* ofSoundPlayer.h */, + E42963652163EDD300A6A9E2 /* ofSoundStream.cpp */, + E42963662163EDD300A6A9E2 /* ofSoundStream.h */, + E429636F2163EDD300A6A9E2 /* ofSoundUtils.h */, + ); + name = sound; + path = ../../../libs/openFrameworks/sound; + sourceTree = SOURCE_ROOT; + }; + E4B69B4A0A3A1720003C02F2 = { + isa = PBXGroup; + children = ( + 8EF01BA62F7876DD00BF0971 /* example_slides */, + 8EF01BA42F7876C700BF0971 /* emotibitSlidePlayerSettings.json */, + E4B6FCAD0C3E899E008CF71C /* openFrameworks-Info.plist */, + E4EB6923138AFD0F00A09F29 /* Project.xcconfig */, + E4B69E1C0A3A1BDC003C02F2 /* src */, + E42962AA2163EDD300A6A9E2 /* openFrameworks */, + BB4B014C10F69532006C3DED /* addons */, + 6948EE371B920CB800B5AC1A /* local_addons */, + E4B69B5B0A3A1756003C02F2 /* EmotiBitSlidePlayerDebug.app */, + ); + sourceTree = ""; + }; + E4B69E1C0A3A1BDC003C02F2 /* src */ = { + isa = PBXGroup; + children = ( + E4B69E1D0A3A1BDC003C02F2 /* main.cpp */, + E4B69E1E0A3A1BDC003C02F2 /* ofApp.cpp */, + E4B69E1F0A3A1BDC003C02F2 /* ofApp.h */, + ); + path = src; + sourceTree = SOURCE_ROOT; + }; + F40E80CB2D443CBA9581DD03 /* libs */ = { + isa = PBXGroup; + children = ( + 977A836DD2C489CCC5E330FF /* jsoncpp */, + ); + name = libs; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + E4B69B5A0A3A1756003C02F2 /* EmotiBitSlidePlayer */ = { + isa = PBXNativeTarget; + buildConfigurationList = E4B69B5F0A3A1757003C02F2 /* Build configuration list for PBXNativeTarget "EmotiBitSlidePlayer" */; + buildPhases = ( + E42962A92163ECCD00A6A9E2 /* ShellScript */, + E4B69B580A3A1756003C02F2 /* Sources */, + E4B69B590A3A1756003C02F2 /* Frameworks */, + E4B6FFFD0C3F9AB9008CF71C /* ShellScript */, + E4C2427710CC5ABF004149E2 /* CopyFiles */, + 8466F1851C04CA0E00918B1C /* ShellScript */, + 8EF01BA32F78768F00BF0971 /* Copy Settings and Example Slides */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = EmotiBitSlidePlayer; + productName = myOFApp; + productReference = E4B69B5B0A3A1756003C02F2 /* EmotiBitSlidePlayerDebug.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + E4B69B4C0A3A1720003C02F2 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0600; + }; + buildConfigurationList = E4B69B4D0A3A1720003C02F2 /* Build configuration list for PBXProject "EmotiBitSlidePlayer" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + English, + Japanese, + French, + German, + ); + mainGroup = E4B69B4A0A3A1720003C02F2; + productRefGroup = E4B69B4A0A3A1720003C02F2; + projectDirPath = ""; + projectRoot = ""; + targets = ( + E4B69B5A0A3A1756003C02F2 /* EmotiBitSlidePlayer */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXShellScriptBuildPhase section */ + 8466F1851C04CA0E00918B1C /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 12; + files = ( + ); + inputPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$GCC_PREPROCESSOR_DEFINITIONS\";\nAPPSTORE=`expr \"$GCC_PREPROCESSOR_DEFINITIONS\" : \".*APPSTORE=\\([0-9]*\\)\"`\nif [ -z \"$APPSTORE\" ] ; then\necho \"Note: Not copying bin/data to App Package or doing App Code signing. Use AppStore target for AppStore distribution\";\nelse\n# Copy bin/data into App/Resources\nrsync -avz --exclude='.DS_Store' \"${SRCROOT}/bin/data/\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/data/\"\n\n# ---- Code Sign App Package ----\n\n# WARNING: You may have to run Clean in Xcode after changing CODE_SIGN_IDENTITY!\n\n# Verify that $CODE_SIGN_IDENTITY is set\nif [ -z \"${CODE_SIGN_IDENTITY}\" ] ; then\necho \"CODE_SIGN_IDENTITY needs to be set for framework code-signing\"\nexit 0\nfi\n\nif [ -z \"${CODE_SIGN_ENTITLEMENTS}\" ] ; then\necho \"CODE_SIGN_ENTITLEMENTS needs to be set for framework code-signing!\"\n\nif [ \"${CONFIGURATION}\" = \"Release\" ] ; then\nexit 1\nelse\n# Code-signing is optional for non-release builds.\nexit 0\nfi\nfi\n\nITEMS=\"\"\n\nFRAMEWORKS_DIR=\"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}\"\necho \"$FRAMEWORKS_DIR\"\nif [ -d \"$FRAMEWORKS_DIR\" ] ; then\nFRAMEWORKS=$(find \"${FRAMEWORKS_DIR}\" -depth -type d -name \"*.framework\" -or -name \"*.dylib\" -or -name \"*.bundle\" | sed -e \"s/\\(.*framework\\)/\\1\\/Versions\\/A\\//\")\nRESULT=$?\nif [[ $RESULT != 0 ]] ; then\nexit 1\nfi\n\nITEMS=\"${FRAMEWORKS}\"\nfi\n\nLOGINITEMS_DIR=\"${TARGET_BUILD_DIR}/${CONTENTS_FOLDER_PATH}/Library/LoginItems/\"\nif [ -d \"$LOGINITEMS_DIR\" ] ; then\nLOGINITEMS=$(find \"${LOGINITEMS_DIR}\" -depth -type d -name \"*.app\")\nRESULT=$?\nif [[ $RESULT != 0 ]] ; then\nexit 1\nfi\n\nITEMS=\"${ITEMS}\"$'\\n'\"${LOGINITEMS}\"\nfi\n\n# Prefer the expanded name, if available.\nCODE_SIGN_IDENTITY_FOR_ITEMS=\"${EXPANDED_CODE_SIGN_IDENTITY_NAME}\"\nif [ \"${CODE_SIGN_IDENTITY_FOR_ITEMS}\" = \"\" ] ; then\n# Fall back to old behavior.\nCODE_SIGN_IDENTITY_FOR_ITEMS=\"${CODE_SIGN_IDENTITY}\"\nfi\n\necho \"Identity:\"\necho \"${CODE_SIGN_IDENTITY_FOR_ITEMS}\"\n\necho \"Entitlements:\"\necho \"${CODE_SIGN_ENTITLEMENTS}\"\n\necho \"Found:\"\necho \"${ITEMS}\"\n\n# Change the Internal Field Separator (IFS) so that spaces in paths will not cause problems below.\nSAVED_IFS=$IFS\nIFS=$(echo -en \"\\n\\b\")\n\n# Loop through all items.\nfor ITEM in $ITEMS;\ndo\necho \"Stripping invalid archs '${ITEM}'\"\nlipo -extract x86_64 \"${ITEM}\" -o \"${ITEM}\"\necho \"Signing '${ITEM}'\"\ncodesign --force --verbose --sign \"${CODE_SIGN_IDENTITY_FOR_ITEMS}\" --entitlements \"${CODE_SIGN_ENTITLEMENTS}\" \"${ITEM}\"\nRESULT=$?\nif [[ $RESULT != 0 ]] ; then\necho \"Failed to sign '${ITEM}'.\"\nIFS=$SAVED_IFS\nexit 1\nfi\ndone\n\n# Restore $IFS.\nIFS=$SAVED_IFS\n\nfi\n"; + }; + E42962A92163ECCD00A6A9E2 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "xcodebuild -project \"$OF_PATH/libs/openFrameworksCompiled/project/osx/openFrameworksLib.xcodeproj\" -target openFrameworks -configuration \"${CONFIGURATION}\""; + }; + E4B6FFFD0C3F9AB9008CF71C /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "mkdir -p \"$TARGET_BUILD_DIR/$PRODUCT_NAME.app/Contents/Resources/\"\n# Copy default icon file into App/Resources\nrsync -aved \"$ICON_FILE\" \"$TARGET_BUILD_DIR/$PRODUCT_NAME.app/Contents/Resources/\"\n# Copy libfmod and change install directory for fmod to run\nrsync -aved \"$OF_PATH/libs/fmod/lib/osx/libfmod.dylib\" \"$TARGET_BUILD_DIR/$PRODUCT_NAME.app/Contents/Frameworks/\";\n# Not needed as we now call install_name_tool -id @loader_path/../Frameworks/libfmod.dylib libfmod.dylib on the dylib directly which prevents the need for calling every post build - keeping here for reference and possible legacy usage \n# install_name_tool -change @rpath/libfmod.dylib @executable_path/../Frameworks/libfmod.dylib \"$TARGET_BUILD_DIR/$PRODUCT_NAME.app/Contents/MacOS/$PRODUCT_NAME\";\n\necho \"$GCC_PREPROCESSOR_DEFINITIONS\";\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + E4B69B580A3A1756003C02F2 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E4B69E200A3A1BDC003C02F2 /* main.cpp in Sources */, + E4B69E210A3A1BDC003C02F2 /* ofApp.cpp in Sources */, + FB84AAF8D1B7A95266DB5C09 /* jsoncpp.cpp in Sources */, + BEDFEE7400C58EA4E412B757 /* ofxJSONElement.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 99FA3DBB1C7456C400CFA0EE /* AppStore */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E4EB6923138AFD0F00A09F29 /* Project.xcconfig */; + buildSettings = { + CONFIGURATION_BUILD_DIR = "$(SRCROOT)/bin/"; + COPY_PHASE_STRIP = YES; + DEAD_CODE_STRIPPING = YES; + GCC_AUTO_VECTORIZATION = YES; + GCC_ENABLE_SSE3_EXTENSIONS = YES; + GCC_ENABLE_SUPPLEMENTAL_SSE3_INSTRUCTIONS = YES; + GCC_INLINES_ARE_PRIVATE_EXTERN = NO; + GCC_OPTIMIZATION_LEVEL = 3; + "GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = "DISTRIBUTION=1"; + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_UNROLL_LOOPS = YES; + GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = YES; + GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO = NO; + GCC_WARN_ALLOW_INCOMPLETE_PROTOCOL = NO; + GCC_WARN_UNINITIALIZED_AUTOS = NO; + GCC_WARN_UNUSED_VALUE = NO; + GCC_WARN_UNUSED_VARIABLE = NO; + HEADER_SEARCH_PATHS = ( + "$(OF_CORE_HEADERS)", + src, + ../../../addons/ofxJSON/libs, + ../../../addons/ofxJSON/libs/jsoncpp, + ../../../addons/ofxJSON/libs/jsoncpp/include, + ../../../addons/ofxJSON/libs/jsoncpp/include/json, + ../../../addons/ofxJSON/libs/jsoncpp/src, + ../../../addons/ofxJSON/src, + ); + MACOSX_DEPLOYMENT_TARGET = 10.9; + OTHER_CODE_SIGN_FLAGS = "--deep"; + OTHER_CPLUSPLUSFLAGS = "-D__MACOSX_CORE__"; + SDKROOT = macosx; + }; + name = AppStore; + }; + 99FA3DBC1C7456C400CFA0EE /* AppStore */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E4EB6923138AFD0F00A09F29 /* Project.xcconfig */; + buildSettings = { + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = YES; + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_MODEL_TUNING = NONE; + "GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = "APPSTORE=1"; + HEADER_SEARCH_PATHS = ( + "$(OF_CORE_HEADERS)", + src, + ../../../addons/ofxJSON/libs, + ../../../addons/ofxJSON/libs/jsoncpp, + ../../../addons/ofxJSON/libs/jsoncpp/include, + ../../../addons/ofxJSON/libs/jsoncpp/include/json, + ../../../addons/ofxJSON/libs/jsoncpp/src, + ../../../addons/ofxJSON/src, + ); + ICON = EmotiBit.icns; + ICON_FILE = ../EmotiBitIcons/macOS/EmotiBit.icns; + ICON_FILE_PATH = ../EmotiBitIcons/macOS; + ICON_NAME_DEBUG = EmotiBit.icns; + ICON_NAME_RELEASE = EmotiBit.icns; + INFOPLIST_FILE = "openFrameworks-Info.plist"; + INSTALL_PATH = /Applications; + LIBRARY_SEARCH_PATHS = "$(inherited)"; + OTHER_LDFLAGS = ( + "$(OF_CORE_LIBS)", + "$(OF_CORE_FRAMEWORKS)", + "$(LIB_OF)", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = app; + baseConfigurationReference = E4EB6923138AFD0F00A09F29; + }; + name = AppStore; + }; + E4B69B4E0A3A1720003C02F2 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E4EB6923138AFD0F00A09F29 /* Project.xcconfig */; + buildSettings = { + CONFIGURATION_BUILD_DIR = "$(SRCROOT)/bin/"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + GCC_AUTO_VECTORIZATION = YES; + GCC_ENABLE_SSE3_EXTENSIONS = YES; + GCC_ENABLE_SUPPLEMENTAL_SSE3_INSTRUCTIONS = YES; + GCC_INLINES_ARE_PRIVATE_EXTERN = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = YES; + GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO = NO; + GCC_WARN_ALLOW_INCOMPLETE_PROTOCOL = NO; + GCC_WARN_UNINITIALIZED_AUTOS = NO; + GCC_WARN_UNUSED_VALUE = NO; + GCC_WARN_UNUSED_VARIABLE = NO; + HEADER_SEARCH_PATHS = ( + "$(OF_CORE_HEADERS)", + src, + ../../../addons/ofxJSON/libs, + ../../../addons/ofxJSON/libs/jsoncpp, + ../../../addons/ofxJSON/libs/jsoncpp/include, + ../../../addons/ofxJSON/libs/jsoncpp/include/json, + ../../../addons/ofxJSON/libs/jsoncpp/src, + ../../../addons/ofxJSON/src, + ); + MACOSX_DEPLOYMENT_TARGET = 10.9; + ONLY_ACTIVE_ARCH = YES; + OTHER_CODE_SIGN_FLAGS = "--deep"; + OTHER_CPLUSPLUSFLAGS = "-D__MACOSX_CORE__"; + SDKROOT = macosx; + }; + name = Debug; + }; + E4B69B4F0A3A1720003C02F2 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E4EB6923138AFD0F00A09F29 /* Project.xcconfig */; + buildSettings = { + CONFIGURATION_BUILD_DIR = "$(SRCROOT)/bin/"; + COPY_PHASE_STRIP = YES; + DEAD_CODE_STRIPPING = YES; + GCC_AUTO_VECTORIZATION = YES; + GCC_ENABLE_SSE3_EXTENSIONS = YES; + GCC_ENABLE_SUPPLEMENTAL_SSE3_INSTRUCTIONS = YES; + GCC_INLINES_ARE_PRIVATE_EXTERN = NO; + GCC_OPTIMIZATION_LEVEL = 3; + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_UNROLL_LOOPS = YES; + GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = YES; + GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO = NO; + GCC_WARN_ALLOW_INCOMPLETE_PROTOCOL = NO; + GCC_WARN_UNINITIALIZED_AUTOS = NO; + GCC_WARN_UNUSED_VALUE = NO; + GCC_WARN_UNUSED_VARIABLE = NO; + HEADER_SEARCH_PATHS = ( + "$(OF_CORE_HEADERS)", + src, + ../../../addons/ofxJSON/libs, + ../../../addons/ofxJSON/libs/jsoncpp, + ../../../addons/ofxJSON/libs/jsoncpp/include, + ../../../addons/ofxJSON/libs/jsoncpp/include/json, + ../../../addons/ofxJSON/libs/jsoncpp/src, + ../../../addons/ofxJSON/src, + ); + MACOSX_DEPLOYMENT_TARGET = 10.9; + OTHER_CODE_SIGN_FLAGS = "--deep"; + OTHER_CPLUSPLUSFLAGS = "-D__MACOSX_CORE__"; + SDKROOT = macosx; + }; + name = Release; + }; + E4B69B600A3A1757003C02F2 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E4EB6923138AFD0F00A09F29 /* Project.xcconfig */; + buildSettings = { + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; + GCC_DYNAMIC_NO_PIC = NO; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_MODEL_TUNING = NONE; + HEADER_SEARCH_PATHS = ( + "$(OF_CORE_HEADERS)", + src, + ../../../addons/ofxJSON/libs, + ../../../addons/ofxJSON/libs/jsoncpp, + ../../../addons/ofxJSON/libs/jsoncpp/include, + ../../../addons/ofxJSON/libs/jsoncpp/include/json, + ../../../addons/ofxJSON/libs/jsoncpp/src, + ../../../addons/ofxJSON/src, + ); + ICON = EmotiBit.icns; + ICON_FILE = ../EmotiBitIcons/macOS/EmotiBit.icns; + ICON_FILE_PATH = ../EmotiBitIcons/macOS; + ICON_NAME_DEBUG = EmotiBit.icns; + ICON_NAME_RELEASE = EmotiBit.icns; + INFOPLIST_FILE = "openFrameworks-Info.plist"; + INSTALL_PATH = /Applications; + LIBRARY_SEARCH_PATHS = "$(inherited)"; + OTHER_LDFLAGS = ( + "$(OF_CORE_LIBS)", + "$(OF_CORE_FRAMEWORKS)", + "$(LIB_OF_DEBUG)", + ); + PRODUCT_NAME = "$(TARGET_NAME)Debug"; + WRAPPER_EXTENSION = app; + }; + name = Debug; + }; + E4B69B610A3A1757003C02F2 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E4EB6923138AFD0F00A09F29 /* Project.xcconfig */; + buildSettings = { + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = YES; + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_MODEL_TUNING = NONE; + HEADER_SEARCH_PATHS = ( + "$(OF_CORE_HEADERS)", + src, + ../../../addons/ofxJSON/libs, + ../../../addons/ofxJSON/libs/jsoncpp, + ../../../addons/ofxJSON/libs/jsoncpp/include, + ../../../addons/ofxJSON/libs/jsoncpp/include/json, + ../../../addons/ofxJSON/libs/jsoncpp/src, + ../../../addons/ofxJSON/src, + ); + ICON = EmotiBit.icns; + ICON_FILE = ../EmotiBitIcons/macOS/EmotiBit.icns; + ICON_FILE_PATH = ../EmotiBitIcons/macOS; + ICON_NAME_DEBUG = EmotiBit.icns; + ICON_NAME_RELEASE = EmotiBit.icns; + INFOPLIST_FILE = "openFrameworks-Info.plist"; + INSTALL_PATH = /Applications; + LIBRARY_SEARCH_PATHS = "$(inherited)"; + OTHER_LDFLAGS = ( + "$(OF_CORE_LIBS)", + "$(OF_CORE_FRAMEWORKS)", + "$(LIB_OF)", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = app; + baseConfigurationReference = E4EB6923138AFD0F00A09F29; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + E4B69B4D0A3A1720003C02F2 /* Build configuration list for PBXProject "EmotiBitSlidePlayer" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E4B69B4E0A3A1720003C02F2 /* Debug */, + E4B69B4F0A3A1720003C02F2 /* Release */, + 99FA3DBB1C7456C400CFA0EE /* AppStore */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + E4B69B5F0A3A1757003C02F2 /* Build configuration list for PBXNativeTarget "EmotiBitSlidePlayer" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E4B69B600A3A1757003C02F2 /* Debug */, + E4B69B610A3A1757003C02F2 /* Release */, + 99FA3DBC1C7456C400CFA0EE /* AppStore */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = E4B69B4C0A3A1720003C02F2 /* Project object */; +} diff --git a/EmotiBitSlidePlayer/EmotiBitSlidePlayer.xcodeproj/xcshareddata/xcschemes/Debug.xcscheme b/EmotiBitSlidePlayer/EmotiBitSlidePlayer.xcodeproj/xcshareddata/xcschemes/Debug.xcscheme new file mode 100644 index 00000000..57212aa3 --- /dev/null +++ b/EmotiBitSlidePlayer/EmotiBitSlidePlayer.xcodeproj/xcshareddata/xcschemes/Debug.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/EmotiBitSlidePlayer/EmotiBitSlidePlayer.xcodeproj/xcshareddata/xcschemes/Release.xcscheme b/EmotiBitSlidePlayer/EmotiBitSlidePlayer.xcodeproj/xcshareddata/xcschemes/Release.xcscheme new file mode 100644 index 00000000..7f18b3f5 --- /dev/null +++ b/EmotiBitSlidePlayer/EmotiBitSlidePlayer.xcodeproj/xcshareddata/xcschemes/Release.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/EmotiBitSlidePlayer/Makefile b/EmotiBitSlidePlayer/Makefile new file mode 100644 index 00000000..177e1726 --- /dev/null +++ b/EmotiBitSlidePlayer/Makefile @@ -0,0 +1,13 @@ +# Attempt to load a config.make file. +# If none is found, project defaults in config.project.make will be used. +ifneq ($(wildcard config.make),) + include config.make +endif + +# make sure the the OF_ROOT location is defined +ifndef OF_ROOT + OF_ROOT=$(realpath ../../..) +endif + +# call the project makefile! +include $(OF_ROOT)/libs/openFrameworksCompiled/project/makefileCommon/compile.project.mk diff --git a/EmotiBitSlidePlayer/Project.xcconfig b/EmotiBitSlidePlayer/Project.xcconfig new file mode 100644 index 00000000..40adc89a --- /dev/null +++ b/EmotiBitSlidePlayer/Project.xcconfig @@ -0,0 +1,18 @@ +//THE PATH TO THE ROOT OF OUR OF PATH RELATIVE TO THIS PROJECT. +//THIS NEEDS TO BE DEFINED BEFORE CoreOF.xcconfig IS INCLUDED +OF_PATH = ../../.. + +//THIS HAS ALL THE HEADER AND LIBS FOR OF CORE +#include "../../../libs/openFrameworksCompiled/project/osx/CoreOF.xcconfig" + +//ICONS - NEW IN 0072 +ICON_NAME_DEBUG = icon-debug.icns +ICON_NAME_RELEASE = icon.icns +ICON_FILE_PATH = $(OF_PATH)/libs/openFrameworksCompiled/project/osx/ + +//IF YOU WANT AN APP TO HAVE A CUSTOM ICON - PUT THEM IN YOUR DATA FOLDER AND CHANGE ICON_FILE_PATH to: +//ICON_FILE_PATH = bin/data/ + +OTHER_CFLAGS = $(OF_CORE_CFLAGS) +OTHER_LDFLAGS = $(OF_CORE_LIBS) $(OF_CORE_FRAMEWORKS) +HEADER_SEARCH_PATHS = $(OF_CORE_HEADERS) diff --git a/EmotiBitSlidePlayer/README.md b/EmotiBitSlidePlayer/README.md new file mode 100644 index 00000000..42cfe534 --- /dev/null +++ b/EmotiBitSlidePlayer/README.md @@ -0,0 +1,73 @@ +# EmotiBit SlidePlayer + +Displays timed sequences of image slide sets and logs all events to a CSV file. Intended for use in EmotiBit data-collection sessions. + +## Settings file + +On first launch the app copies `emotibitSlidePlayerSettings.json` to `~/Documents/EmotiBit/` and reads it from there on every subsequent run. Edit that copy to configure the app. + +### Global slide settings + +| Key | Description | +|-----|-------------| +| `background` | Image shown during the off-interval between slides | +| `slideSetIntroSlide` | Intro image shown at the start of every set (unless overridden per-set) | +| `pauseOnSetIntroSlide` | If `true`, app pauses on the intro slide until manually advanced | +| `slideSetIntroSlideTimeMin_msec` / `Max_msec` | Random display time for the intro slide (set min == max for a fixed duration) | +| `slideOrderRandomization` | Randomize slide order within a set | +| `maxSlidesPerSet` | How many slides to show per set | +| `slideOnTimeMin_msec` / `Max_msec` | Random display time per slide (set both to 0 to advance only via key press) | +| `slideOffTimeMin_msec` / `Max_msec` | Random blank-screen time between slides | + +### App settings + +| Key | Description | +|-----|-------------| +| `startFullScreen` | Start in full-screen mode | +| `startPaused` | Start with the slideshow paused | +| `logFileDirectory` | Directory for CSV log output (leave empty to log to the current directory) | + +### Keyboard controls (all keys re-bindable in settings) + +| Default key | Action | +|-------------|--------| +| `F` | Toggle full screen | +| `N` | Next slide | +| `B` | Previous slide | +| `P` | Pause / resume | +| `R` | Restart slideshow from beginning | +| `T` | Restart current slide set | +| `S` | Reload settings file | +| `L` | Set log file directory | + +### Slide sets + +Each entry in `slideSets` points to a directory of images (`.jpg`, `.jpeg`, `.png`, `.bmp`). Per-set settings override the global defaults; unspecified keys inherit from `globalSlideSettings`. + +```json +"slideSets": [ + { + "slideDirectory": "example_slides/set-1", + "slideOrderRandomization": false, + "slideSetIntroSlide": "example_slides/set-1/set_1-Intro_slide.png" + } +] +``` + +Image paths can be relative (resolved from the app's `data/` directory) or absolute. + +## Event log + +A CSV file (`dateTime,event,details`) is written to `logFileDirectory` on each run. Logged events include `SLIDE_ON`, `SLIDE_OFF`, `KEY_PRESS`, `APP_END`, and others. + +> **Note:** If `logFileDirectory` does not exist the log file will silently fail to open. Ensure the directory exists before launching. + +## Known TODOs + +- `startFullScreen` and `startPaused` settings are parsed but not yet implemented. +- `S` (load settings) and `L` (set log directory) keyboard commands are not yet implemented. +- Pressing `B` at the start of a set does not cross back to the previous set; it stops at the first slide of the current set. +- No end slide is shown when the last slide set finishes — the app exits immediately. +- Supported image extensions (jpg/jpeg/png/bmp) are hardcoded; other formats are not loaded. +- Time-elapsed tracking on key press only accounts for the ON phase, not the OFF phase. +- Log directory existence is not validated before opening the log file. diff --git a/EmotiBitSlidePlayer/addons.make b/EmotiBitSlidePlayer/addons.make new file mode 100644 index 00000000..ab5217ba --- /dev/null +++ b/EmotiBitSlidePlayer/addons.make @@ -0,0 +1 @@ +ofxJSON diff --git a/EmotiBitSlidePlayer/bin/data/emotibitSlidePlayerSettings.json b/EmotiBitSlidePlayer/bin/data/emotibitSlidePlayerSettings.json new file mode 100644 index 00000000..b7450b3b --- /dev/null +++ b/EmotiBitSlidePlayer/bin/data/emotibitSlidePlayerSettings.json @@ -0,0 +1,44 @@ +{ + "globalSlideSettings": { + "background": "example_slides/default_slides/default_background.png", + "slideSetIntroSlide": "example_slides/default_slides/default_intro_slide.png", + "pauseOnSetIntroSlide": true, + "slideSetIntroSlideTimeMin_msec": 4000, // durations are randomized in between min and max + "slideSetIntroSlideTimeMax_msec": 10000, + "slideOrderRandomization": true, + "maxSlidesPerSet": 5, // number of slides to play from each set + "slideOnTimeMin_msec": 3000, + "slideOnTimeMax_msec": 3000, + "slideOffTimeMin_msec": 1000, + "slideOffTimeMax_msec": 2000 + }, + "appSettings": { + "keyboardControls": { + "toggleFullScreen": "F", + "nextSlide": "N", + "previousSlide": "B", + "pauseSlideshowToggle": "P", + "restartSlideshow": "R", + "restartSlideSet": "T", + "loadSettingsFile": "S", + "setLogFileDirectory": "L" + }, + "logFileDirectory": "", + "startFullScreen": true, + "startPaused": true + }, + "slideSets": [ + // At the beginning of each slideSet the globalSlideSettings are reloaded + // and then the slideSet-specific settings are loaded + { + "slideDirectory": "example_slides/set-1", + "slideOrderRandomization": false, + "slideSetIntroSlide": "example_slides/set-1/set_1-Intro_slide.png" + }, + { + "slideDirectory": "example_slides/set-2", + "slideOrderRandomization": false, + "background": "example_slides/set-2/set_2-background.png" + } + ] +} diff --git a/EmotiBitSlidePlayer/bin/data/example_slides/default_slides/default_background.png b/EmotiBitSlidePlayer/bin/data/example_slides/default_slides/default_background.png new file mode 100644 index 00000000..0cabc43c Binary files /dev/null and b/EmotiBitSlidePlayer/bin/data/example_slides/default_slides/default_background.png differ diff --git a/EmotiBitSlidePlayer/bin/data/example_slides/default_slides/default_intro_slide.png b/EmotiBitSlidePlayer/bin/data/example_slides/default_slides/default_intro_slide.png new file mode 100644 index 00000000..43d422f8 Binary files /dev/null and b/EmotiBitSlidePlayer/bin/data/example_slides/default_slides/default_intro_slide.png differ diff --git a/EmotiBitSlidePlayer/bin/data/example_slides/set-1/set_1-Intro_slide.png b/EmotiBitSlidePlayer/bin/data/example_slides/set-1/set_1-Intro_slide.png new file mode 100644 index 00000000..491f6753 Binary files /dev/null and b/EmotiBitSlidePlayer/bin/data/example_slides/set-1/set_1-Intro_slide.png differ diff --git a/EmotiBitSlidePlayer/bin/data/example_slides/set-1/set_1-slide_1.png b/EmotiBitSlidePlayer/bin/data/example_slides/set-1/set_1-slide_1.png new file mode 100644 index 00000000..542f2745 Binary files /dev/null and b/EmotiBitSlidePlayer/bin/data/example_slides/set-1/set_1-slide_1.png differ diff --git a/EmotiBitSlidePlayer/bin/data/example_slides/set-1/set_1-slide_2.png b/EmotiBitSlidePlayer/bin/data/example_slides/set-1/set_1-slide_2.png new file mode 100644 index 00000000..1719a802 Binary files /dev/null and b/EmotiBitSlidePlayer/bin/data/example_slides/set-1/set_1-slide_2.png differ diff --git a/EmotiBitSlidePlayer/bin/data/example_slides/set-1/set_1-slide_3.png b/EmotiBitSlidePlayer/bin/data/example_slides/set-1/set_1-slide_3.png new file mode 100644 index 00000000..6224c165 Binary files /dev/null and b/EmotiBitSlidePlayer/bin/data/example_slides/set-1/set_1-slide_3.png differ diff --git a/EmotiBitSlidePlayer/bin/data/example_slides/set-1/set_1-slide_4.png b/EmotiBitSlidePlayer/bin/data/example_slides/set-1/set_1-slide_4.png new file mode 100644 index 00000000..5c426892 Binary files /dev/null and b/EmotiBitSlidePlayer/bin/data/example_slides/set-1/set_1-slide_4.png differ diff --git a/EmotiBitSlidePlayer/bin/data/example_slides/set-2/set_2-background.png b/EmotiBitSlidePlayer/bin/data/example_slides/set-2/set_2-background.png new file mode 100644 index 00000000..9bb09463 Binary files /dev/null and b/EmotiBitSlidePlayer/bin/data/example_slides/set-2/set_2-background.png differ diff --git a/EmotiBitSlidePlayer/bin/data/example_slides/set-2/set_2-slide_1.png b/EmotiBitSlidePlayer/bin/data/example_slides/set-2/set_2-slide_1.png new file mode 100644 index 00000000..ef55c284 Binary files /dev/null and b/EmotiBitSlidePlayer/bin/data/example_slides/set-2/set_2-slide_1.png differ diff --git a/EmotiBitSlidePlayer/bin/data/example_slides/set-2/set_2-slide_2.png b/EmotiBitSlidePlayer/bin/data/example_slides/set-2/set_2-slide_2.png new file mode 100644 index 00000000..909d8c11 Binary files /dev/null and b/EmotiBitSlidePlayer/bin/data/example_slides/set-2/set_2-slide_2.png differ diff --git a/EmotiBitSlidePlayer/bin/data/example_slides/set-2/set_2-slide_3.png b/EmotiBitSlidePlayer/bin/data/example_slides/set-2/set_2-slide_3.png new file mode 100644 index 00000000..4402d07e Binary files /dev/null and b/EmotiBitSlidePlayer/bin/data/example_slides/set-2/set_2-slide_3.png differ diff --git a/EmotiBitSlidePlayer/compile_flags.txt b/EmotiBitSlidePlayer/compile_flags.txt new file mode 100644 index 00000000..241bdc93 --- /dev/null +++ b/EmotiBitSlidePlayer/compile_flags.txt @@ -0,0 +1,37 @@ +-std=c++23 +-xc++ +-DMACOSX +-DGL_SILENCE_DEPRECATION=1 +-DGLES_SILENCE_DEPRECATION=1 +-DCOREVIDEO_SILENCE_GL_DEPRECATION=1 +-DGLM_FORCE_CTOR_INIT +-DGLM_ENABLE_EXPERIMENTAL +-I/Users/nnair/CFL/dev/EmotiBit/Software/of_v0.12.1_osx_release/libs/openFrameworks +-I/Users/nnair/CFL/dev/EmotiBit/Software/of_v0.12.1_osx_release/libs/openFrameworks/3d +-I/Users/nnair/CFL/dev/EmotiBit/Software/of_v0.12.1_osx_release/libs/openFrameworks/app +-I/Users/nnair/CFL/dev/EmotiBit/Software/of_v0.12.1_osx_release/libs/openFrameworks/communication +-I/Users/nnair/CFL/dev/EmotiBit/Software/of_v0.12.1_osx_release/libs/openFrameworks/events +-I/Users/nnair/CFL/dev/EmotiBit/Software/of_v0.12.1_osx_release/libs/openFrameworks/gl +-I/Users/nnair/CFL/dev/EmotiBit/Software/of_v0.12.1_osx_release/libs/openFrameworks/graphics +-I/Users/nnair/CFL/dev/EmotiBit/Software/of_v0.12.1_osx_release/libs/openFrameworks/math +-I/Users/nnair/CFL/dev/EmotiBit/Software/of_v0.12.1_osx_release/libs/openFrameworks/sound +-I/Users/nnair/CFL/dev/EmotiBit/Software/of_v0.12.1_osx_release/libs/openFrameworks/types +-I/Users/nnair/CFL/dev/EmotiBit/Software/of_v0.12.1_osx_release/libs/openFrameworks/utils +-I/Users/nnair/CFL/dev/EmotiBit/Software/of_v0.12.1_osx_release/libs/openFrameworks/video +-I/Users/nnair/CFL/dev/EmotiBit/Software/of_v0.12.1_osx_release/libs/freetype/include +-I/Users/nnair/CFL/dev/EmotiBit/Software/of_v0.12.1_osx_release/libs/freetype/include/freetype2 +-I/Users/nnair/CFL/dev/EmotiBit/Software/of_v0.12.1_osx_release/libs/glew/include +-I/Users/nnair/CFL/dev/EmotiBit/Software/of_v0.12.1_osx_release/libs/FreeImage/include +-I/Users/nnair/CFL/dev/EmotiBit/Software/of_v0.12.1_osx_release/libs/tess2/include +-I/Users/nnair/CFL/dev/EmotiBit/Software/of_v0.12.1_osx_release/libs/cairo/include +-I/Users/nnair/CFL/dev/EmotiBit/Software/of_v0.12.1_osx_release/libs/rtAudio/include +-I/Users/nnair/CFL/dev/EmotiBit/Software/of_v0.12.1_osx_release/libs/glfw/include +-I/Users/nnair/CFL/dev/EmotiBit/Software/of_v0.12.1_osx_release/libs/utf8/include +-I/Users/nnair/CFL/dev/EmotiBit/Software/of_v0.12.1_osx_release/libs/json/include +-I/Users/nnair/CFL/dev/EmotiBit/Software/of_v0.12.1_osx_release/libs/glm/include +-I/Users/nnair/CFL/dev/EmotiBit/Software/of_v0.12.1_osx_release/libs/curl/include +-I/Users/nnair/CFL/dev/EmotiBit/Software/of_v0.12.1_osx_release/libs/openssl/include +-I/Users/nnair/CFL/dev/EmotiBit/Software/of_v0.12.1_osx_release/libs/uriparser/include +-I/Users/nnair/CFL/dev/EmotiBit/Software/of_v0.12.1_osx_release/libs/pugixml/include +-I/Users/nnair/CFL/dev/EmotiBit/Software/of_v0.12.1_osx_release/libs/brotli/include +-Isrc diff --git a/EmotiBitSlidePlayer/config.make b/EmotiBitSlidePlayer/config.make new file mode 100644 index 00000000..5a719bc9 --- /dev/null +++ b/EmotiBitSlidePlayer/config.make @@ -0,0 +1,142 @@ +################################################################################ +# CONFIGURE PROJECT MAKEFILE (optional) +# This file is where we make project specific configurations. +################################################################################ + +################################################################################ +# OF ROOT +# The location of your root openFrameworks installation +# (default) OF_ROOT = ../../.. +################################################################################ +OF_ROOT = /Users/cfl/dev/emotibit/software/local_dev/of_v0.11.2_osx_release + +################################################################################ +# PROJECT ROOT +# The location of the project - a starting place for searching for files +# (default) PROJECT_ROOT = . (this directory) +# +################################################################################ +# PROJECT_ROOT = . + +################################################################################ +# PROJECT SPECIFIC CHECKS +# This is a project defined section to create internal makefile flags to +# conditionally enable or disable the addition of various features within +# this makefile. For instance, if you want to make changes based on whether +# GTK is installed, one might test that here and create a variable to check. +################################################################################ +# None + +################################################################################ +# PROJECT EXTERNAL SOURCE PATHS +# These are fully qualified paths that are not within the PROJECT_ROOT folder. +# Like source folders in the PROJECT_ROOT, these paths are subject to +# exlclusion via the PROJECT_EXLCUSIONS list. +# +# (default) PROJECT_EXTERNAL_SOURCE_PATHS = (blank) +# +# Note: Leave a leading space when adding list items with the += operator +################################################################################ +# PROJECT_EXTERNAL_SOURCE_PATHS = + +################################################################################ +# PROJECT EXCLUSIONS +# These makefiles assume that all folders in your current project directory +# and any listed in the PROJECT_EXTERNAL_SOURCH_PATHS are are valid locations +# to look for source code. The any folders or files that match any of the +# items in the PROJECT_EXCLUSIONS list below will be ignored. +# +# Each item in the PROJECT_EXCLUSIONS list will be treated as a complete +# string unless teh user adds a wildcard (%) operator to match subdirectories. +# GNU make only allows one wildcard for matching. The second wildcard (%) is +# treated literally. +# +# (default) PROJECT_EXCLUSIONS = (blank) +# +# Will automatically exclude the following: +# +# $(PROJECT_ROOT)/bin% +# $(PROJECT_ROOT)/obj% +# $(PROJECT_ROOT)/%.xcodeproj +# +# Note: Leave a leading space when adding list items with the += operator +################################################################################ +# PROJECT_EXCLUSIONS = + +################################################################################ +# PROJECT LINKER FLAGS +# These flags will be sent to the linker when compiling the executable. +# +# (default) PROJECT_LDFLAGS = -Wl,-rpath=./libs +# +# Note: Leave a leading space when adding list items with the += operator +################################################################################ + +# Currently, shared libraries that are needed are copied to the +# $(PROJECT_ROOT)/bin/libs directory. The following LDFLAGS tell the linker to +# add a runtime path to search for those shared libraries, since they aren't +# incorporated directly into the final executable application binary. +# TODO: should this be a default setting? +# PROJECT_LDFLAGS=-Wl,-rpath=./libs + +################################################################################ +# PROJECT DEFINES +# Create a space-delimited list of DEFINES. The list will be converted into +# CFLAGS with the "-D" flag later in the makefile. +# +# (default) PROJECT_DEFINES = (blank) +# +# Note: Leave a leading space when adding list items with the += operator +################################################################################ +# PROJECT_DEFINES = + +################################################################################ +# PROJECT CFLAGS +# This is a list of fully qualified CFLAGS required when compiling for this +# project. These CFLAGS will be used IN ADDITION TO the PLATFORM_CFLAGS +# defined in your platform specific core configuration files. These flags are +# presented to the compiler BEFORE the PROJECT_OPTIMIZATION_CFLAGS below. +# +# (default) PROJECT_CFLAGS = (blank) +# +# Note: Before adding PROJECT_CFLAGS, note that the PLATFORM_CFLAGS defined in +# your platform specific configuration file will be applied by default and +# further flags here may not be needed. +# +# Note: Leave a leading space when adding list items with the += operator +################################################################################ +# PROJECT_CFLAGS = + +################################################################################ +# PROJECT OPTIMIZATION CFLAGS +# These are lists of CFLAGS that are target-specific. While any flags could +# be conditionally added, they are usually limited to optimization flags. +# These flags are added BEFORE the PROJECT_CFLAGS. +# +# PROJECT_OPTIMIZATION_CFLAGS_RELEASE flags are only applied to RELEASE targets. +# +# (default) PROJECT_OPTIMIZATION_CFLAGS_RELEASE = (blank) +# +# PROJECT_OPTIMIZATION_CFLAGS_DEBUG flags are only applied to DEBUG targets. +# +# (default) PROJECT_OPTIMIZATION_CFLAGS_DEBUG = (blank) +# +# Note: Before adding PROJECT_OPTIMIZATION_CFLAGS, please note that the +# PLATFORM_OPTIMIZATION_CFLAGS defined in your platform specific configuration +# file will be applied by default and further optimization flags here may not +# be needed. +# +# Note: Leave a leading space when adding list items with the += operator +################################################################################ +# PROJECT_OPTIMIZATION_CFLAGS_RELEASE = +# PROJECT_OPTIMIZATION_CFLAGS_DEBUG = + +################################################################################ +# PROJECT COMPILERS +# Custom compilers can be set for CC and CXX +# (default) PROJECT_CXX = (blank) +# (default) PROJECT_CC = (blank) +# Note: Leave a leading space when adding list items with the += operator +################################################################################ +# PROJECT_CXX = +# PROJECT_CC = diff --git a/EmotiBitSlidePlayer/icon.rc b/EmotiBitSlidePlayer/icon.rc new file mode 100644 index 00000000..1ac16e0a --- /dev/null +++ b/EmotiBitSlidePlayer/icon.rc @@ -0,0 +1,9 @@ +// Icon Resource Definition +#define MAIN_ICON 102 + +MAIN_ICON ICON "..\EmotiBitIcons\icoFiles\EmotiBitLogo.ico" +//#if defined(_DEBUG) +//MAIN_ICON ICON "..\EmotiBitIcons\icoFiles\EmotiBitLogo.ico" +//#else +//MAIN_ICON ICON "..\EmotiBitIcons\icoFiles\EmotiBitLogo.ico" +//#endif diff --git a/EmotiBitSlidePlayer/openFrameworks-Info.plist b/EmotiBitSlidePlayer/openFrameworks-Info.plist new file mode 100644 index 00000000..ba13aec8 --- /dev/null +++ b/EmotiBitSlidePlayer/openFrameworks-Info.plist @@ -0,0 +1,28 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + cc.openFrameworks.${EXECUTABLE_NAME} + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + APPL + CFBundleSignature + ???? + CFBundleVersion + 1.0 + CFBundleIconFile + ${ICON} + NSCameraUsageDescription + This app needs to access the camera + NSMicrophoneUsageDescription + This app needs to access the microphone + NSHighResolutionCapable + + + diff --git a/EmotiBitSlidePlayer/src/main.cpp b/EmotiBitSlidePlayer/src/main.cpp new file mode 100644 index 00000000..9887d902 --- /dev/null +++ b/EmotiBitSlidePlayer/src/main.cpp @@ -0,0 +1,7 @@ +#include "ofApp.h" +#include "ofMain.h" + +int main() { + ofSetupOpenGL(1024, 768, OF_WINDOW); + ofRunApp(new ofApp()); +} diff --git a/EmotiBitSlidePlayer/src/ofApp.cpp b/EmotiBitSlidePlayer/src/ofApp.cpp new file mode 100644 index 00000000..88c99566 --- /dev/null +++ b/EmotiBitSlidePlayer/src/ofApp.cpp @@ -0,0 +1,691 @@ +#include "ofApp.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "json/json.h" +#include "ofAppRunner.h" +#include "ofFileUtils.h" +#include "ofImage.h" +#include "ofUtils.h" + +// ── Setup +// ───────────────────────────────────────────────────────────────────── + +void ofApp::ensureSettingsFile() +{ + std::string docs_dir = ofFilePath::join( + ofFilePath::join( + ofFilePath::join(ofFilePath::getUserHomeDir(), "Documents"), + "EmotiBit"), + "EmotiBitSlidePlayer"); + std::string target_path = ofFilePath::join(docs_dir, settings_file_name_); + if (!ofFile(target_path).exists()) + { + ofDirectory::createDirectory(docs_dir, false, true); + ofFile source(ofToDataPath(settings_file_name_), ofFile::Reference, + false); + source.copyTo(target_path, false, false); + } + else + { + std::cout << "Settings file already exists at: " << target_path + << ". Use this file to specify app settings" << std::endl; + } + settings_file_name_ = target_path; +} + +void ofApp::setup() +{ +#ifdef TARGET_OSX + ofSetDataPathRoot("../Resources"); +#endif + ensureSettingsFile(); + ofSetLogLevel(OF_LOG_SILENT); + if (!loadAppSettings()) + { + std::cerr << "App settings file not found. Quitting" << std::endl; + ofExit(); + } + startLogToFile(); + logEvent("APP_START", "settings=" + settings_file_name_ + + " log_dir=" + app_settings_.log_file_directory_); + logEvent("SETTINGS_JSON", settings_json_); + for (int i = 0; i < (int)app_settings_.slide_sets_.size(); i++) + { + const auto& ss = app_settings_.slide_sets_[i]; + logEvent("SETTINGS_LOAD", + "set_index=" + std::to_string(i) + + " dir=" + ss.slide_directory_ + " max_slides=" + + std::to_string(ss.settings_.max_slides_per_set_) + + " randomize=" + + std::to_string(ss.settings_.slide_order_randomization_)); + } +} + +bool ofApp::loadAppSettings() +{ + ofFile emotibit_slide_player_settings(settings_file_name_); + std::string json_str; + if (emotibit_slide_player_settings.exists()) + { + ofBuffer buf = emotibit_slide_player_settings.readToBuffer(); + json_str = buf.getText(); + } + else + { + std::cerr << "Error: file not found - " + << emotibit_slide_player_settings.getAbsolutePath() + << std::endl; + return false; + } + + Json::Reader reader; + Json::Value root; + if (!reader.parse(json_str, root)) + { + std::cerr << "JSON parsing failed" << std::endl; + return false; + } + Json::FastWriter writer; + settings_json_ = writer.write(root); + // FastWriter appends a trailing newline — strip it for clean CSV embedding + if (!settings_json_.empty() && settings_json_.back() == '\n') + { + settings_json_.pop_back(); + } + return parseSettings(root); +} + +bool ofApp::parseSettings(const Json::Value& settings) +{ + if (settings.isMember("appSettings")) + { + const Json::Value& app = settings["appSettings"]; + + if (app.isMember("logFileDirectory") && + !app["logFileDirectory"].asString().empty()) + { + app_settings_.log_file_directory_ = + app["logFileDirectory"].asString(); + } + else + { + app_settings_.log_file_directory_ = ofFilePath::join( + ofFilePath::join( + ofFilePath::join( + ofFilePath::join(ofFilePath::getUserHomeDir(), + "Documents"), + "EmotiBit"), + "EmotiBitSlidePlayer"), + "log"); + std::cerr + << "Warning: log file directory not specified. Using default: " + << app_settings_.log_file_directory_ << std::endl; + } + + if (app.isMember("startFullScreen") && app["startFullScreen"].isBool()) + { + app_settings_.start_full_screen_ = app["startFullScreen"].asBool(); + } + else + { + std::cerr << "Error: startFullScreen not specified in settings file" + << std::endl; + } + + if (app.isMember("startPaused") && app["startPaused"].isBool()) + { + app_settings_.start_paused_ = app["startPaused"].asBool(); + } + else + { + std::cerr << "Error: startPaused not specified in settings file" + << std::endl; + } + + if (app.isMember("keyboardControls")) + { + const Json::Value& kc = app["keyboardControls"]; + auto load_key = [](const Json::Value& node, const std::string& key, + char& target) + { + if (node.isMember(key) && !node[key].asString().empty()) + { + target = node[key].asString()[0]; + } + }; + load_key(kc, "toggleFullScreen", + app_settings_.keyboard_controls_.toggle_full_screen_); + load_key(kc, "nextSlide", + app_settings_.keyboard_controls_.next_slide_); + load_key(kc, "previousSlide", + app_settings_.keyboard_controls_.previous_slide_); + load_key(kc, "pauseSlideshowToggle", + app_settings_.keyboard_controls_.pause_slide_show_toggle_); + load_key(kc, "restartSlideshow", + app_settings_.keyboard_controls_.restart_slide_show_); + load_key(kc, "restartSlideSet", + app_settings_.keyboard_controls_.restart_slide_set_); + load_key(kc, "loadSettingsFile", + app_settings_.keyboard_controls_.load_settings_file_); + load_key(kc, "setLogFileDirectory", + app_settings_.keyboard_controls_.set_log_file_directory_); + } + else + { + std::cerr + << "Error: keyboardControls not specified in settings file" + << std::endl; + } + } + else + { + std::cerr << "Error: appSettings not specified" << std::endl; + } + + auto resolve_path = [](const std::string& path) -> std::string + { return ofFilePath::isAbsolute(path) ? path : ofToDataPath(path, true); }; + + auto load_slide_settings = + [&resolve_path](const Json::Value& node, AppSettings::SlideSettings& s) + { + if (node.isMember("background")) + { + s.background_ = resolve_path(node["background"].asString()); + } + if (node.isMember("slideSetIntroSlide")) + { + s.slide_set_intro_slide_ = + resolve_path(node["slideSetIntroSlide"].asString()); + } + if (node.isMember("pauseOnSetIntroSlide")) + { + s.pause_on_set_intro_slide_ = node["pauseOnSetIntroSlide"].asBool(); + } + if (node.isMember("slideSetIntroSlideTimeMin_msec")) + { + s.slide_set_intro_slide_time_min_msec_ = + node["slideSetIntroSlideTimeMin_msec"].asFloat(); + } + if (node.isMember("slideSetIntroSlideTimeMax_msec")) + { + s.slide_set_intro_slide_time_max_msec_ = + node["slideSetIntroSlideTimeMax_msec"].asFloat(); + } + if (node.isMember("slideOrderRandomization")) + { + s.slide_order_randomization_ = + node["slideOrderRandomization"].asBool(); + } + if (node.isMember("maxSlidesPerSet")) + { + s.max_slides_per_set_ = node["maxSlidesPerSet"].asInt(); + } + if (node.isMember("slideOnTimeMin_msec")) + { + s.slide_on_time_min_msec_ = node["slideOnTimeMin_msec"].asFloat(); + } + if (node.isMember("slideOnTimeMax_msec")) + { + s.slide_on_time_max_msec_ = node["slideOnTimeMax_msec"].asFloat(); + } + if (node.isMember("slideOffTimeMin_msec")) + { + s.slide_off_time_min_msec_ = node["slideOffTimeMin_msec"].asFloat(); + } + if (node.isMember("slideOffTimeMax_msec")) + { + s.slide_off_time_max_msec_ = node["slideOffTimeMax_msec"].asFloat(); + } + }; + + if (settings.isMember("globalSlideSettings")) + { + load_slide_settings(settings["globalSlideSettings"], + app_settings_.global_slide_settings_); + } + else + { + std::cerr << "Error: globalSlideSettings not specified" << std::endl; + } + + if (settings.isMember("slideSets")) + { + uint16_t num_slide_sets = settings["slideSets"].size(); + for (int i = 0; i < num_slide_sets; i++) + { + AppSettings::SlideSet ss; + if (settings["slideSets"][i].isMember("slideDirectory")) + { + ss.slide_directory_ = resolve_path( + settings["slideSets"][i]["slideDirectory"].asString()); + // start from global settings, then apply per-set overrides + ss.settings_ = app_settings_.global_slide_settings_; + load_slide_settings(settings["slideSets"][i], ss.settings_); + app_settings_.slide_sets_.push_back(ss); + } + else + { + std::cerr << "Warning: slide set " << i + << " directory not specified. Skipping" << std::endl; + } + } + } + else + { + std::cerr << "Error: slideSets not specified" << std::endl; + } + return true; +} + +bool ofApp::startLogToFile() +{ + // TODO: Perform a check on if the log directory exists because it + // results in a silent fail if the directory does not exist + std::string log_file_name = + app_settings_.log_file_directory_ + get_timestamp_() + ".csv"; + event_log_.open(log_file_name); + if (!event_log_.is_open()) + { + std::cerr << "Failed to open log file: " << log_file_name << std::endl; + return false; + } + event_log_ << "dateTime,event,details\n"; + log_stream_ = &event_log_; + return true; +} + +void ofApp::logEvent(const std::string& event, const std::string& details) +{ + std::string timestamp = get_timestamp_(); + if (log_stream_ != nullptr) + { + *log_stream_ << timestamp << ',' << event << ',' << '"' << details + << '"' << '\n'; + log_stream_->flush(); + } + std::cout << timestamp << ' ' << event << ' ' << details << '\n'; +} + +// ── Per-frame +// ───────────────────────────────────────────────────────────────── + +void ofApp::update() +{ + if (should_exit_) + { + ofExit(); + return; + } + updateCurrentState(); +} + +void ofApp::updateCurrentState() +{ + if (CurrentState::SlideState::kSlidePause == current_state_.slide_state_) + { + return; + } + if (current_state_.init_new_set_) + { + current_state_.slide_set_index_++; + if (current_state_.slide_set_index_ >= + (int)app_settings_.slide_sets_.size()) + { + logEvent("APP_END", "reason=end_of_slide_show"); + // TODO: consider if we want an end slide + should_exit_ = true; + return; + } + current_state_.slide_index_ = + -1; // gets updated in the changeSlide call + current_state_.slide_settings_ = + app_settings_.slide_sets_[current_state_.slide_set_index_] + .settings_; + current_state_.slide_dir_ = + app_settings_.slide_sets_[current_state_.slide_set_index_] + .slide_directory_; + + current_state_.slide_paths_ = + load_slide_paths_(current_state_.slide_dir_); + + if (current_state_.slide_paths_.empty()) + { + logEvent( + "WARNING", + "set_index=" + std::to_string(current_state_.slide_set_index_) + + " reason=no_slides_found dir=" + current_state_.slide_dir_); + current_state_.init_new_set_ = true; // skip to next set + return; + } + + // extract intro and background slides before shuffle/resize so they + // are not counted towards num_slides and not randomized + auto extract_from_set = [&](const std::string& path) -> std::string + { + if (path.empty()) + { + return ""; + } + std::string filename = ofFilePath::getFileName(path); + auto it = std::find_if( + current_state_.slide_paths_.begin(), + current_state_.slide_paths_.end(), + [&filename](const std::string& p) + { return ofFilePath::getFileName(p) == filename; }); + if (it != current_state_.slide_paths_.end()) + { + std::string extracted = *it; + current_state_.slide_paths_.erase(it); + return extracted; + } + return path; // outside the set directory — use the path directly + }; + + std::string intro_path = extract_from_set( + current_state_.slide_settings_.slide_set_intro_slide_); + extract_from_set(current_state_.slide_settings_.background_); + + if (current_state_.slide_settings_.slide_order_randomization_) + { + std::shuffle(current_state_.slide_paths_.begin(), + current_state_.slide_paths_.end(), + std::mt19937{std::random_device{}()}); + } + int num_slides = + std::min(current_state_.slide_settings_.max_slides_per_set_, + (int)current_state_.slide_paths_.size()); + current_state_.slide_paths_.resize(num_slides); + + if (!intro_path.empty()) + { + current_state_.slide_paths_.insert( + current_state_.slide_paths_.begin(), intro_path); + } + + if (!current_state_.slide_settings_.background_.empty()) + { + load_background_image_(current_state_.slide_settings_.background_); + } + else + { + background_image_.clear(); + } + current_state_.init_new_set_ = false; + std::string slide_list; + for (const auto& path : current_state_.slide_paths_) + { + slide_list += path + "|"; + } + logEvent( + "SLIDE_SET_INIT", + "set_index=" + std::to_string(current_state_.slide_set_index_) + + " intro_slide=" + intro_path + " background=" + + current_state_.slide_settings_.background_ + " slide_count=" + + std::to_string(current_state_.slide_paths_.size()) + + " slides=" + slide_list); + // use existing machinery to advance to the next slide + changeSlide(1); + } + + // SlideState management + if (CurrentState::SlideState::kSlideOn == current_state_.slide_state_) + { + if (current_state_.state_times_.on_time_ != 0) + { + // if on time is set to 0, only key strokes can change + // slides + if ((float)get_time_msec_() - + (float)current_state_.phase_start_msec_ > + current_state_.state_times_.on_time_) + { + current_state_.slide_state_ = + CurrentState::SlideState::kSlideOff; + current_state_.phase_start_msec_ = get_time_msec_(); + logEvent( + "SLIDE_OFF", + "set_index=" + + std::to_string(current_state_.slide_set_index_) + + " slide_index=" + + std::to_string(current_state_.slide_index_) + + " OFF_TIME=" + + std::to_string(current_state_.state_times_.off_time_)); + } + } + } + if (CurrentState::SlideState::kSlideOff == current_state_.slide_state_) + { + if (current_state_.slide_index_ == 0) + { + // increment without wait if this is an intro slide + // no background for intro slide + changeSlide(1); + } + else + { + if ((float)get_time_msec_() - + (float)current_state_.phase_start_msec_ > + current_state_.state_times_.off_time_) + { + changeSlide(1); + } + } + } +} + +void ofApp::changeSlide(int delta) +{ + current_state_.slide_index_ = current_state_.slide_index_ + delta; + if (current_state_.slide_index_ < 0) + { + // TODO: if we press B enough times, we dont cross over to the previous + // set, we stay at the beginning of the current set + current_state_.slide_index_ = 0; + } + float on_time_min = current_state_.slide_settings_.slide_on_time_min_msec_; + float on_time_max = current_state_.slide_settings_.slide_on_time_max_msec_; + float off_time_min = + current_state_.slide_settings_.slide_off_time_min_msec_; + float off_time_max = + current_state_.slide_settings_.slide_off_time_max_msec_; + if (current_state_.slide_index_ == 0) + { + // if first slide, use the intro slide values + // NOTE: There is no OFF time for intro slides + on_time_min = + current_state_.slide_settings_.slide_set_intro_slide_time_min_msec_; + on_time_max = + current_state_.slide_settings_.slide_set_intro_slide_time_max_msec_; + } + if (on_time_min == on_time_max) + { + current_state_.state_times_.on_time_ = on_time_max; + } + else + { + std::mt19937 rng{std::random_device{}()}; + std::uniform_int_distribution dist((int)on_time_min, + (int)on_time_max); + current_state_.state_times_.on_time_ = (float)dist(rng); + } + if (current_state_.slide_index_ == 0) + { + // NOTE: intro slide has no off time + current_state_.state_times_.off_time_ = 0; + } + else + { + if (off_time_min == off_time_max) + { + current_state_.state_times_.off_time_ = off_time_max; + } + else + { + std::mt19937 rng{std::random_device{}()}; + std::uniform_int_distribution dist((int)off_time_min, + (int)off_time_max); + current_state_.state_times_.off_time_ = (float)dist(rng); + } + } + current_state_.phase_start_msec_ = get_time_msec_(); + if (current_state_.slide_index_ >= (int)current_state_.slide_paths_.size()) + { + current_state_.init_new_set_ = true; + } + else + { + current_state_.slide_state_ = CurrentState::SlideState::kSlideOn; + load_slide_image_( + current_state_.slide_paths_[current_state_.slide_index_]); + logEvent( + "SLIDE_ON", + "set_index=" + std::to_string(current_state_.slide_set_index_) + + " slide_index=" + std::to_string(current_state_.slide_index_) + + " path=" + + current_state_.slide_paths_[current_state_.slide_index_] + + " ON_TIME=" + + std::to_string(current_state_.state_times_.on_time_)); + } +} + +// ── Rendering +// ───────────────────────────────────────────────────────────────── + +void ofApp::draw() +{ + if (CurrentState::SlideState::kSlideOff == current_state_.slide_state_) + { + drawImageFitted(background_image_); + } + else + { + drawImageFitted(current_slide_image_); + } +} + +void ofApp::drawImageFitted(const ofImage& img) +{ + if (!img.isAllocated()) + { + ofBackground(0); + return; + } + float scale = std::min((float)ofGetWidth() / img.getWidth(), + (float)ofGetHeight() / img.getHeight()); + float draw_w = img.getWidth() * scale; + float draw_h = img.getHeight() * scale; + float x = ((float)ofGetWidth() - draw_w) / 2.0f; + float y = ((float)ofGetHeight() - draw_h) / 2.0f; + ofBackground(0); + img.draw(x, y, draw_w, draw_h); +} + +// ── Input +// ───────────────────────────────────────────────────────────────────── + +void ofApp::keyPressed(int key) +{ + // TODO: Time since phase change should be calculate for boath ON and OFF + // Modifier keys (Shift, Ctrl, etc.) produce large keycodes outside the + // printable ASCII range — skip them to avoid spurious log entries. + if (key > 127) + { + return; + } + const char kKeyChar = static_cast(toupper(key)); + logEvent("KEY_PRESS", std::string("key=") + kKeyChar); + if (app_settings_.keyboard_controls_.next_slide_ == kKeyChar) + { + changeSlide(1); + } + if (app_settings_.keyboard_controls_.previous_slide_ == kKeyChar) + { + changeSlide(-1); + } + if (app_settings_.keyboard_controls_.restart_slide_set_ == kKeyChar) + { + changeSlide((-1 * current_state_.slide_index_)); + } + if (app_settings_.keyboard_controls_.restart_slide_show_ == kKeyChar) + { + current_state_.slide_set_index_ = -1; + current_state_.init_new_set_ = true; + } + if (app_settings_.keyboard_controls_.pause_slide_show_toggle_ == kKeyChar) + { + if (current_state_.slide_state_ != + CurrentState::SlideState::kSlidePause) + { + current_state_.slide_state_before_pause_ = + current_state_.slide_state_; + current_state_.slide_state_ = CurrentState::SlideState::kSlidePause; + current_state_.time_since_phase_start_on_pause_ = + (float)get_time_msec_() - + (float)current_state_.phase_start_msec_; + logEvent( + "PAUSE", + "set_index=" + std::to_string(current_state_.slide_set_index_) + + " slide_index=" + + std::to_string(current_state_.slide_index_)); + } + else + { + current_state_.slide_state_ = + current_state_.slide_state_before_pause_; + current_state_.phase_start_msec_ = + get_time_msec_() - + (uint64_t)current_state_.time_since_phase_start_on_pause_; + logEvent( + "RESUME", + "set_index=" + std::to_string(current_state_.slide_set_index_) + + " slide_index=" + + std::to_string(current_state_.slide_index_)); + } + } +} + +void ofApp::keyReleased(int key) +{ +} + +void ofApp::mouseMoved(int x, int y) +{ +} + +void ofApp::mouseDragged(int x, int y, int button) +{ +} + +void ofApp::mousePressed(int x, int y, int button) +{ +} + +void ofApp::mouseReleased(int x, int y, int button) +{ +} + +void ofApp::mouseEntered(int x, int y) +{ +} + +void ofApp::mouseExited(int x, int y) +{ +} + +void ofApp::windowResized(int w, int h) +{ +} + +void ofApp::dragEvent(ofDragInfo dragInfo) +{ +} + +void ofApp::gotMessage(ofMessage msg) +{ +} diff --git a/EmotiBitSlidePlayer/src/ofApp.h b/EmotiBitSlidePlayer/src/ofApp.h new file mode 100644 index 00000000..659294b4 --- /dev/null +++ b/EmotiBitSlidePlayer/src/ofApp.h @@ -0,0 +1,317 @@ +#pragma once + +#include +#include + +#include "ofMain.h" + +namespace Json +{ +class Value; +} + +/// @brief Main application class for the EmotiBit slide player. +/// +/// Manages loading settings, displaying timed sequences of image slide sets, +/// logging events to a CSV file, and handling keyboard input. +class ofApp : public ofBaseApp +{ + public: + // ── Data types + // ──────────────────────────────────────────────────────────── + + /// @brief All settings loaded from the JSON settings file. + typedef struct AppSettings + { + /// @brief Directory where the CSV log file will be written. + std::string log_file_directory_ = ""; + // TODO: Need to implement the full screen and start paused + // implementation + /// @brief Whether the app starts in full-screen mode. + bool start_full_screen_ = true; + /// @brief Whether the slide show starts paused. + bool start_paused_ = true; + // TODO: implement load_settings and set_log_directory + + /// @brief Keyboard shortcut bindings. + typedef struct KeyboardControls + { + /// @brief Key to toggle full-screen mode. + char toggle_full_screen_ = 'F'; + /// @brief Key to advance to the next slide. + char next_slide_ = 'N'; + /// @brief Key to go back to the previous slide. + char previous_slide_ = 'B'; + /// @brief Key to toggle pause/resume. + char pause_slide_show_toggle_ = 'P'; + /// @brief Key to restart the entire slide show from the beginning. + char restart_slide_show_ = 'R'; + /// @brief Key to restart the current slide set from its first + /// slide. + char restart_slide_set_ = 'T'; + /// @brief Key to reload the settings file. + char load_settings_file_ = 'S'; + /// @brief Key to set the log file directory at runtime. + char set_log_file_directory_ = 'L'; + } KeyboardControls; + + /// @brief Active keyboard control bindings. + KeyboardControls keyboard_controls_; + + /// @brief Display and timing settings for a slide set. + typedef struct SlideSettisgs + { + /// @brief Path to the background image shown during slide-off + /// intervals. + std::string background_ = ""; + /// @brief Path to the intro slide shown at the start of the set. + std::string slide_set_intro_slide_ = ""; + /// @brief Whether to pause automatically on the intro slide. + bool pause_on_set_intro_slide_ = true; + /// @brief Lower bound (ms) of the random display time for the intro + /// slide. If equal to max, the value is used directly. + float slide_set_intro_slide_time_min_msec_ = 0; + /// @brief Upper bound (ms) of the random display time for the intro + /// slide. If equal to min, the value is used directly. + float slide_set_intro_slide_time_max_msec_ = 0; + /// @brief Whether to randomize slide order within the set. + bool slide_order_randomization_ = true; + /// @brief Maximum number of slides to show per set. + int max_slides_per_set_ = 5; + /// @brief Lower bound (ms) of the random on-time per slide. + /// If equal to max, the value is used directly. If both are 0, + /// only key presses advance the slide. + float slide_on_time_min_msec_ = 0; + /// @brief Upper bound (ms) of the random on-time per slide. + /// If equal to min, the value is used directly. If both are 0, + /// only key presses advance the slide. + float slide_on_time_max_msec_ = 0; + /// @brief Lower bound (ms) of the random off-time between slides. + /// If equal to max, the value is used directly. + float slide_off_time_min_msec_ = 0; + /// @brief Upper bound (ms) of the random off-time between slides. + /// If equal to min, the value is used directly. + float slide_off_time_max_msec_ = 0; + } SlideSettings; + + /// @brief Global slide settings used as defaults for all sets. + SlideSettisgs global_slide_settings_; + + /// @brief A single slide set — a directory of images and its settings. + typedef struct SlideSet + { + /// @brief Path to the directory containing the slide images. + std::string slide_directory_ = ""; + /// @brief Per-set settings, inheriting from global with optional + /// overrides. + SlideSettings settings_; + } SlideSet; + + /// @brief Ordered list of slide sets to display during the session. + std::vector slide_sets_; + } AppSettings; + + /// @brief Runtime state of the slide show. + typedef struct CurrentState + { + /// @brief Whether the current set needs to be initialised on the next + /// update. + bool init_new_set_ = true; + /// @brief Index of the active slide set. -1 = uninitialized. + int slide_set_index_ = -1; + /// @brief Index of the active slide within the current set. + int slide_index_ = 0; + /// @brief Path to the active slide set directory. + std::string slide_dir_; + /// @brief Ordered list of image paths for the current set. + std::vector slide_paths_; + /// @brief Active display/timing settings for the current set. + AppSettings::SlideSettings slide_settings_; + /// @brief Timestamp (ms) when the current ON or OFF phase started. + uint64_t phase_start_msec_ = 0; + + /// @brief Phase states for the slide show state machine. + enum class SlideState + { + kSlideOn, ///< Slide image is visible. + kSlideOff, ///< Screen is blank between slides. + kSlidePause, ///< Show is paused by the user. + kSlideIntro ///< Intro slide is visible. + }; + + /// @brief Holds the randomly chosen on and off durations for the + /// current slide, sampled once per slide change from the min/max + /// ranges in SlideSettings. + typedef struct StateTimes + { + /// @brief Randomly chosen duration (ms) the slide stays visible. + float on_time_ = 0; + /// @brief Randomly chosen duration (ms) the screen stays blank + /// after the slide goes off. + float off_time_ = 0; + } StateTimes; + /// @brief Active on/off durations for the current slide. + StateTimes state_times_; + /// @brief Current phase state. + SlideState slide_state_ = SlideState::kSlideOn; + /// @brief State that was active before the show was paused. + SlideState slide_state_before_pause_ = SlideState::kSlideOn; + /// @brief Elapsed time in the current phase at the moment pause was + /// triggered (ms). + float time_since_phase_start_on_pause_ = 0; + } CurrentState; + + // ── Member variables + // ────────────────────────────────────────────────────── + + /// @brief Settings loaded from the JSON file. + AppSettings app_settings_; + /// @brief Live runtime state of the slide show. + CurrentState current_state_; + /// @brief When true, @c update() will call @c ofExit(). + bool should_exit_ = false; + /// @brief File name of the JSON settings file. + std::string settings_file_name_ = "emotibitSlidePlayerSettings.json"; + /// @brief Compact single-line JSON snapshot of loaded settings, written to + /// the log on startup. + std::string settings_json_; + + /// @brief Returns the current time in milliseconds. Injectable for testing. + std::function get_time_msec_ = []() + { return static_cast(ofGetElapsedTimeMillis()); }; + + /// @brief Returns a formatted timestamp string. Injectable for testing. + std::function get_timestamp_ = []() + { return ofGetTimestampString(); }; + + /// @brief Loads and returns sorted image paths from a directory. Injectable + /// for testing. + std::function(const std::string&)> + load_slide_paths_ = [](const std::string& dir) + { + if (!ofDirectory::doesDirectoryExist(dir)) + { + std::cerr << "Error: slide directory not found: " << dir + << std::endl; + return std::vector{}; + } + ofDirectory d(dir); + // TODO: consider if other file extensions should be allowed when + // listing the files + d.allowExt("jpg"); + d.allowExt("jpeg"); + d.allowExt("png"); + d.allowExt("bmp"); + d.listDir(); + d.sort(); + std::vector paths; + for (int i = 0; i < (int)d.size(); i++) + { + paths.push_back(d.getPath(i)); + } + return paths; + }; + + /// @brief Loads an image into @c current_slide_image_. Injectable for + /// testing. + std::function load_slide_image_ = + [this](const std::string& path) { current_slide_image_.load(path); }; + + /// @brief Loads an image into @c background_image_. Injectable for testing. + std::function load_background_image_ = + [this](const std::string& path) { background_image_.load(path); }; + + /// @brief Currently displayed slide image. + ofImage current_slide_image_; + /// @brief Background image shown during slide-off intervals. + ofImage background_image_; + /// @brief File stream for the CSV event log. + std::ofstream event_log_; + /// @brief Pointer to the active log output stream. Points to @c event_log_ + /// after file open. Injectable for testing. + std::ostream* log_stream_ = nullptr; + + // ── Setup + // ───────────────────────────────────────────────────────────────── + + /// @brief Ensures the settings file exists in ~/Documents/EmotiBit/. + /// Copies it from the app bundle Resources if not present, then updates + /// settings_file_name_ to the absolute path. + void ensureSettingsFile(); + + /// @brief openFrameworks setup callback. Loads settings and opens the log + /// file. + void setup(); + + /// @brief Reads the JSON settings file from disk and calls parseSettings(). + /// @return True on success, false if the file is missing or unparseable. + bool loadAppSettings(); + + /// @brief Parses a JSON value into @c app_settings_. + /// @param settings Root JSON object from the settings file. + /// @return Always returns true; individual missing keys are reported to + /// stderr. + bool parseSettings(const Json::Value& settings); + + /// @brief Opens the CSV log file and writes the header row. + /// @return True if the file was opened successfully. + bool startLogToFile(); + + /// @brief Writes a single event row to the CSV log and echoes it to stdout. + /// @param event Event name (e.g. "SLIDE_ON", "KEY_PRESS"). + /// @param details Space-separated key=value pairs describing the event. + void logEvent(const std::string& event, const std::string& details); + + // ── Per-frame + // ───────────────────────────────────────────────────────────── + + /// @brief openFrameworks per-frame update callback. + void update(); + + /// @brief Drives the slide show state machine: initialises sets, manages + /// ON/OFF transitions, and advances slides based on timing. + void updateCurrentState(); + + /// @brief Advances or retreats the current slide index by @p delta. + /// @param delta Positive to go forward, negative to go backward. + void changeSlide(int delta); + + // ── Rendering + // ───────────────────────────────────────────────────────────── + + /// @brief openFrameworks per-frame draw callback. + void draw(); + + /// @brief Draws @p img centred and aspect-ratio-fitted to the window. + /// Fills the window with black if the image is not allocated. + /// @param img Image to draw. + void drawImageFitted(const ofImage& img); + + // ── Input + // ───────────────────────────────────────────────────────────────── + + /// @brief Handles key-down events for slide navigation, pause, and restart. + /// @param key ASCII key code. Modifier keys (>127) are ignored. + void keyPressed(int key); + + /// @brief openFrameworks key-release callback (unused). + void keyReleased(int key); + /// @brief openFrameworks mouse-move callback (unused). + void mouseMoved(int x, int y); + /// @brief openFrameworks mouse-drag callback (unused). + void mouseDragged(int x, int y, int button); + /// @brief openFrameworks mouse-press callback (unused). + void mousePressed(int x, int y, int button); + /// @brief openFrameworks mouse-release callback (unused). + void mouseReleased(int x, int y, int button); + /// @brief openFrameworks mouse-enter callback (unused). + void mouseEntered(int x, int y); + /// @brief openFrameworks mouse-exit callback (unused). + void mouseExited(int x, int y); + /// @brief openFrameworks window-resize callback (unused). + void windowResized(int w, int h); + /// @brief openFrameworks drag-and-drop callback (unused). + void dragEvent(ofDragInfo dragInfo); + /// @brief openFrameworks message callback (unused). + void gotMessage(ofMessage msg); +}; diff --git a/EmotiBitSlidePlayer/tests/test_parseSettings.cpp b/EmotiBitSlidePlayer/tests/test_parseSettings.cpp new file mode 100644 index 00000000..21c7c484 --- /dev/null +++ b/EmotiBitSlidePlayer/tests/test_parseSettings.cpp @@ -0,0 +1,100 @@ +#include + +#include "json/json.h" +#include "ofApp.h" +#include "ofFileUtils.h" + +TEST_CASE("global slide settings are loaded", "[parseSettings]") +{ + Json::Value root; + root["globalSlideSettings"]["background"] = "./bg.jpg"; + root["globalSlideSettings"]["maxSlidesPerSet"] = 3; + root["globalSlideSettings"]["slideOrderRandomization"] = false; + root["globalSlideSettings"]["slideOnTimeMin_msec"] = 1000.0f; + root["globalSlideSettings"]["slideOnTimeMax_msec"] = 2000.0f; + + ofApp app; + app.parseSettings(root); + + REQUIRE(app.app_settings_.global_slide_settings_.background_ == ofToDataPath("./bg.jpg", true)); + REQUIRE(app.app_settings_.global_slide_settings_.max_slides_per_set_ == 3); + REQUIRE( + app.app_settings_.global_slide_settings_.slide_order_randomization_ == + false); + REQUIRE(app.app_settings_.global_slide_settings_.slide_on_time_min_msec_ == + 1000.0f); + REQUIRE(app.app_settings_.global_slide_settings_.slide_on_time_max_msec_ == + 2000.0f); +} + +TEST_CASE("per-set settings override global settings", "[parseSettings]") +{ + Json::Value root; + root["globalSlideSettings"]["maxSlidesPerSet"] = 5; + root["globalSlideSettings"]["slideOrderRandomization"] = true; + root["slideSets"][0]["slideDirectory"] = "./set01/"; + root["slideSets"][0]["maxSlidesPerSet"] = 2; // override + + ofApp app; + app.parseSettings(root); + + REQUIRE(app.app_settings_.slide_sets_[0].settings_.max_slides_per_set_ == + 2); + // slideOrderRandomization was not overridden — should inherit global value + REQUIRE( + app.app_settings_.slide_sets_[0].settings_.slide_order_randomization_ == + true); +} + +TEST_CASE("per-set inherits global when not overridden", "[parseSettings]") +{ + Json::Value root; + root["globalSlideSettings"]["maxSlidesPerSet"] = 5; + root["slideSets"][0]["slideDirectory"] = "./set01/"; + + ofApp app; + app.parseSettings(root); + + REQUIRE(app.app_settings_.slide_sets_[0].settings_.max_slides_per_set_ == + 5); +} + +TEST_CASE("slide set without slideDirectory is skipped", "[parseSettings]") +{ + Json::Value root; + root["globalSlideSettings"]["maxSlidesPerSet"] = 5; + root["slideSets"][0]["maxSlidesPerSet"] = 2; // no slideDirectory + + ofApp app; + app.parseSettings(root); + + REQUIRE(app.app_settings_.slide_sets_.empty()); +} + +TEST_CASE("app settings are loaded", "[parseSettings]") +{ + Json::Value root; + root["appSettings"]["startFullScreen"] = false; + root["appSettings"]["startPaused"] = false; + root["appSettings"]["logFileDirectory"] = "~/Documents/logs/"; + + ofApp app; + app.parseSettings(root); + + REQUIRE(app.app_settings_.start_full_screen_ == false); + REQUIRE(app.app_settings_.start_paused_ == false); + REQUIRE(app.app_settings_.log_file_directory_ == "~/Documents/logs/"); +} + +TEST_CASE("keyboard controls are loaded", "[parseSettings]") +{ + Json::Value root; + root["appSettings"]["keyboardControls"]["toggleFullScreen"] = "G"; + root["appSettings"]["keyboardControls"]["nextSlide"] = "M"; + + ofApp app; + app.parseSettings(root); + + REQUIRE(app.app_settings_.keyboard_controls_.toggle_full_screen_ == 'G'); + REQUIRE(app.app_settings_.keyboard_controls_.next_slide_ == 'M'); +} diff --git a/EmotiBitSlidePlayer/tests/test_updateCurrentState.cpp b/EmotiBitSlidePlayer/tests/test_updateCurrentState.cpp new file mode 100644 index 00000000..5e20cdb5 --- /dev/null +++ b/EmotiBitSlidePlayer/tests/test_updateCurrentState.cpp @@ -0,0 +1,271 @@ +#include +#include + +#include "ofApp.h" + +// ── Helpers +// ─────────────────────────────────────────────────────────────── + +static ofApp makeApp(std::vector slide_paths, + uint64_t& fake_time, + float slide_on_time_max_msec = 1000.0f, + float slide_off_time_max_msec = 500.0f, + std::ostream* log_stream = nullptr) +{ + ofApp app; + app.log_stream_ = log_stream; + + // inject no-op image loaders + app.load_slide_image_ = [](const std::string&) {}; + app.load_background_image_ = [](const std::string&) {}; + + // inject fake path loader + app.load_slide_paths_ = [slide_paths](const std::string&) + { return slide_paths; }; + + // inject controllable clock and fixed timestamp + app.get_time_msec_ = [&fake_time]() { return fake_time; }; + app.get_timestamp_ = []() { return std::string("2026-01-01T00:00:00"); }; + + // one slide set with the given timing + ofApp::AppSettings::SlideSet ss; + ss.slide_directory_ = "./fake/"; + ss.settings_.slide_on_time_min_msec_ = slide_on_time_max_msec; + ss.settings_.slide_on_time_max_msec_ = slide_on_time_max_msec; + ss.settings_.slide_set_intro_slide_time_min_msec_ = slide_on_time_max_msec; + ss.settings_.slide_set_intro_slide_time_max_msec_ = slide_on_time_max_msec; + ss.settings_.slide_off_time_min_msec_ = slide_off_time_max_msec; + ss.settings_.slide_off_time_max_msec_ = slide_off_time_max_msec; + ss.settings_.slide_order_randomization_ = false; + ss.settings_.max_slides_per_set_ = (int)slide_paths.size(); + app.app_settings_.slide_sets_.push_back(ss); + + return app; +} + +// ── Tests: set initialisation +// ───────────────────────────────────────────────────── + +TEST_CASE("slide set is initialised on first update", "[updateCurrentState]") +{ + uint64_t fake_time = 0; + ofApp app = makeApp({"a.jpg", "b.jpg", "c.jpg"}, fake_time); + + app.updateCurrentState(); + + REQUIRE(app.current_state_.slide_set_index_ == 0); + REQUIRE(app.current_state_.slide_index_ == 0); + REQUIRE(app.current_state_.slide_paths_.size() == 3); + REQUIRE_FALSE(app.current_state_.init_new_set_); +} + +TEST_CASE("slide state is ON after set init", "[updateCurrentState]") +{ + uint64_t fake_time = 0; + ofApp app = makeApp({"a.jpg", "b.jpg"}, fake_time); + + app.updateCurrentState(); + + REQUIRE(app.current_state_.slide_state_ == + ofApp::CurrentState::SlideState::kSlideOn); +} + +// ── Tests: ON → OFF transition +// ──────────────────────────────────────────────── + +TEST_CASE("intro slide advances immediately when ON time expires", + "[updateCurrentState]") +{ + uint64_t fake_time = 0; + ofApp app = makeApp({"intro.jpg", "a.jpg", "b.jpg"}, fake_time, /*on=*/1000.0f); + + app.updateCurrentState(); // init, t=0, index=0 (intro), ON + + fake_time = 1500; + app.updateCurrentState(); // intro expires → skips OFF → index=1, ON + + REQUIRE(app.current_state_.slide_index_ == 1); + REQUIRE(app.current_state_.slide_state_ == + ofApp::CurrentState::SlideState::kSlideOn); +} + +TEST_CASE("slide transitions to OFF after max_on_time", "[updateCurrentState]") +{ + uint64_t fake_time = 0; + ofApp app = + makeApp({"intro.jpg", "a.jpg", "b.jpg"}, fake_time, /*on=*/1000.0f); + + app.updateCurrentState(); // init, t=0, index=0 (intro), ON + + fake_time = 1500; + app.updateCurrentState(); // intro expires → index=1, ON, phase=1500 + + fake_time = 3000; + app.updateCurrentState(); // index=1: 3000-1500=1500 > 1000 → OFF + + REQUIRE(app.current_state_.slide_state_ == + ofApp::CurrentState::SlideState::kSlideOff); +} + +TEST_CASE("slide stays ON before max_on_time elapses", "[updateCurrentState]") +{ + uint64_t fake_time = 0; + ofApp app = + makeApp({"intro.jpg", "a.jpg", "b.jpg"}, fake_time, /*on=*/1000.0f); + + app.updateCurrentState(); // init, index=0 (intro), ON + + fake_time = 1500; + app.updateCurrentState(); // intro expires → index=1, ON, phase=1500 + + fake_time = 2000; + app.updateCurrentState(); // 2000-1500=500 < 1000 → stays ON + + REQUIRE(app.current_state_.slide_state_ == + ofApp::CurrentState::SlideState::kSlideOn); +} + +TEST_CASE("slide stays ON forever when max_on_time is 0", "[updateCurrentState]") +{ + uint64_t fake_time = 0; + ofApp app = + makeApp({"intro.jpg", "a.jpg", "b.jpg"}, fake_time, /*on=*/0.0f); + + app.updateCurrentState(); // init, index=0 (intro), ON — intro time also 0 + + fake_time = 99999; + app.updateCurrentState(); // stays ON forever + + REQUIRE(app.current_state_.slide_state_ == + ofApp::CurrentState::SlideState::kSlideOn); +} + +// ── Tests: OFF → next slide +// ─────────────────────────────────────────────────── + +TEST_CASE("slide advances after max_off_time", "[updateCurrentState]") +{ + uint64_t fake_time = 0; + ofApp app = makeApp({"intro.jpg", "a.jpg", "b.jpg"}, fake_time, + /*on=*/1000.0f, /*off=*/500.0f); + + app.updateCurrentState(); // init, t=0, index=0 (intro), ON + + fake_time = 1500; + app.updateCurrentState(); // intro expires → index=1, ON, phase=1500 + + fake_time = 2600; + app.updateCurrentState(); // index=1 ON: 2600-1500=1100 > 1000 → OFF, phase=2600 + + fake_time = 3200; + app.updateCurrentState(); // OFF: 3200-2600=600 > 500 → index=2, ON + + REQUIRE(app.current_state_.slide_index_ == 2); + REQUIRE(app.current_state_.slide_state_ == + ofApp::CurrentState::SlideState::kSlideOn); +} + +// ── Tests: pause / resume +// ───────────────────────────────────────────────────── + +TEST_CASE("pause stops state transitions", "[keyPressed]") +{ + uint64_t fake_time = 0; + ofApp app = makeApp({"a.jpg", "b.jpg"}, fake_time, /*on=*/1000.0f); + app.load_slide_image_ = [](const std::string&) {}; + + app.updateCurrentState(); // init + + // pause + app.keyPressed('P'); + REQUIRE(app.current_state_.slide_state_ == + ofApp::CurrentState::SlideState::kSlidePause); + + // time passes but state must not change + fake_time = 9999; + app.updateCurrentState(); + + REQUIRE(app.current_state_.slide_state_ == + ofApp::CurrentState::SlideState::kSlidePause); +} + +TEST_CASE("resume restores previous state", "[keyPressed]") +{ + uint64_t fake_time = 0; + ofApp app = makeApp({"a.jpg", "b.jpg"}, fake_time, /*on=*/1000.0f); + + app.updateCurrentState(); // init, state = ON + + app.keyPressed('P'); // pause + app.keyPressed('P'); // resume + + REQUIRE(app.current_state_.slide_state_ == + ofApp::CurrentState::SlideState::kSlideOn); +} + +// ── Tests: manual slide navigation +// ─────────────────────────────────────── + +TEST_CASE("next key advances slide index", "[keyPressed]") +{ + uint64_t fake_time = 0; + ofApp app = makeApp({"a.jpg", "b.jpg", "c.jpg"}, fake_time); + + app.updateCurrentState(); // init + + app.keyPressed('N'); + + REQUIRE(app.current_state_.slide_index_ == 1); +} + +TEST_CASE("previous key does not go below 0", "[keyPressed]") +{ + uint64_t fake_time = 0; + ofApp app = makeApp({"a.jpg", "b.jpg"}, fake_time); + + app.updateCurrentState(); // init, slide_index = 0 + + app.keyPressed('B'); + + REQUIRE(app.current_state_.slide_index_ == 0); +} + +// ── Tests: log output +// ───────────────────────────────────────────────────────── + +TEST_CASE("PAUSE event is written to log stream", "[logEvent]") +{ + uint64_t fake_time = 0; + std::ostringstream log; + ofApp app = makeApp({"a.jpg", "b.jpg"}, fake_time, 1000.0f, 500.0f, &log); + + app.updateCurrentState(); + app.keyPressed('P'); + + REQUIRE(log.str().find("PAUSE") != std::string::npos); +} + +TEST_CASE("RESUME event is written to log stream", "[logEvent]") +{ + uint64_t fake_time = 0; + std::ostringstream log; + ofApp app = makeApp({"a.jpg", "b.jpg"}, fake_time, 1000.0f, 500.0f, &log); + + app.updateCurrentState(); + app.keyPressed('P'); // pause + app.keyPressed('P'); // resume + + REQUIRE(log.str().find("RESUME") != std::string::npos); +} + +TEST_CASE("SLIDE_ON event is written to log stream on advance", "[logEvent]") +{ + uint64_t fake_time = 0; + std::ostringstream log; + ofApp app = makeApp({"a.jpg", "b.jpg"}, fake_time, 1000.0f, 500.0f, &log); + + app.updateCurrentState(); + app.keyPressed('N'); + + REQUIRE(log.str().find("SLIDE_ON") != std::string::npos); +} diff --git a/WORKFLOWS.md b/WORKFLOWS.md deleted file mode 100644 index 89f7b5bc..00000000 --- a/WORKFLOWS.md +++ /dev/null @@ -1,198 +0,0 @@ -# GitHub Workflows Documentation - -This document provides an overview of all GitHub workflows in the ofxEmotiBit repository and explains how they work together to create releases. - -## Workflow Inventory - -### 1. Build Workflows - -#### `build-all-on-macos.yml` -**Trigger:** On every push -**Runner:** Self-hosted macOS -**Purpose:** Builds all three EmotiBit applications on macOS -- Clones repository to OpenFrameworks addons directory -- Builds EmotiBitOscilloscope using Xcode (Release-x86_64 scheme) -- Builds EmotiBitDataParser using Xcode (Release scheme) -- Builds EmotiBitFirmwareInstaller using Xcode (Release scheme) - -#### `build-all-on-win.yaml` -**Trigger:** On every push -**Runner:** Self-hosted Windows -**Purpose:** Builds all three EmotiBit applications on Windows -- Clones repository to OpenFrameworks addons directory -- Builds EmotiBitOscilloscope using MSBuild (Release configuration) -- Builds EmotiBitDataParser using MSBuild (Release configuration) -- Builds EmotiBitFirmwareInstaller using MSBuild (Release configuration) - -#### `linux-workflows.yaml` -**Trigger:** On every push -**Runner:** Self-hosted Linux -**Purpose:** Orchestrates Linux build and test workflows -- Clones repository to OpenFrameworks addons directory -- Calls reusable workflow to build Oscilloscope -- Calls reusable workflow to build DataParser -- Calls reusable workflow to test DataParser - -### 2. Reusable Workflows - -#### `reusable-workflow-build-dataParser-linux.yml` -**Trigger:** Called by other workflows -**Runner:** Self-hosted Linux -**Purpose:** Builds EmotiBitDataParser on Linux using make - -#### `reusable-workflow-build-oscilloscope-linux.yml` -**Trigger:** Called by other workflows -**Runner:** Self-hosted Linux -**Purpose:** Builds EmotiBitOscilloscope on Linux using make - -#### `reusable-workflow-test-dataParser-linux.yml` -**Trigger:** Called by other workflows -**Runner:** Self-hosted Linux -**Purpose:** Runs comprehensive tests on DataParser -- Checks parsed file hash for accuracy -- Finds untested typetags -- Tests parsed output format -- Tests composite payload parsing -- Tests timesync response parsing to RD - -### 3. Quality Assurance Workflows - -#### `check-installer-project-version.yml` -**Trigger:** -- Pull requests to `dev` branch (opened, reopened, synchronized) -- Manual workflow dispatch -**Runner:** Self-hosted Linux -**Purpose:** Validates that the Installer project version is correct by running a test script - -### 4. Artifact Upload Workflows - -#### `upload-build-artifact-mac.yml` -**Trigger:** After `build-all-on-macos.yml` completes on `dev` branch -**Runner:** Self-hosted macOS -**Purpose:** Stages and uploads macOS release artifacts -- Creates staging directory -- Moves application binaries (.app bundles) to staging area -- Copies SiLabs CP210x drivers for macOS -- Uploads artifact as `EmotiBitSoftware-macos` - -#### `upload-build-artifact-win.yml` -**Trigger:** After `build-all-on-win.yaml` or `check-installer-project-version.yml` completes on `dev` branch -**Runner:** Self-hosted Windows -**Purpose:** Builds Windows installer and uploads Windows artifacts -- Builds EmotiBit installer using Visual Studio devenv -- Creates staging directory -- Copies MSI installer and setup.exe to staging area -- Copies SiLabs CP210x drivers for Windows -- Uploads artifact as `EmotiBitSoftware-Windows` - -### 5. Release Workflow - -#### `create-draft-release.yml` -**Trigger:** -- When a PR is merged from `dev` to `master` branch -- Manual workflow dispatch -**Runner:** Self-hosted Linux -**Purpose:** Creates a draft GitHub release with all build artifacts -- Downloads macOS artifacts from `upload-build-artifact-mac.yml` -- Downloads Windows artifacts from `upload-build-artifact-win.yml` -- Extracts version number from `src/ofxEmotiBitVersion.h` -- Generates release notes template with installation instructions -- Creates a sample `config.txt` file for WiFi credentials -- Creates a draft release with: - - Tag: `v{version}` - - Attached files: macOS zip, Windows zip, config.txt - - Pre-filled release notes - ---- - -## Release Process Summary - -The EmotiBit release process is automated through a series of connected GitHub workflows. Here's how they work together: - -### Development Phase (Continuous Integration) - -1. **On Every Push to Any Branch:** - - All three platform-specific build workflows run in parallel: - - `build-all-on-macos.yml` builds on macOS - - `build-all-on-win.yaml` builds on Windows - - `linux-workflows.yaml` builds and tests on Linux - - This ensures code quality and catches build errors early - -2. **On Pull Requests to `dev`:** - - `check-installer-project-version.yml` validates the installer version - - Ensures version consistency before merging - -### Artifact Staging (dev branch only) - -3. **After Successful Builds on `dev` Branch:** - - `upload-build-artifact-mac.yml` triggers after macOS build completes - - Packages .app bundles and drivers - - Uploads as `EmotiBitSoftware-macos` artifact - - `upload-build-artifact-win.yml` triggers after Windows build completes - - Builds the Windows installer (.msi) - - Packages installer and drivers - - Uploads as `EmotiBitSoftware-Windows` artifact - -### Release Creation (master branch) - -4. **When `dev` is Merged to `master`:** - - `create-draft-release.yml` automatically triggers - - Downloads the latest macOS and Windows artifacts - - Extracts version from source code (`ofxEmotiBitVersion.h`) - - Creates a draft release with: - - Auto-generated tag: `v{version}` - - Both platform packages attached - - Template release notes (to be filled in manually) - - Sample WiFi configuration file - -### Manual Steps - -5. **Developer Completes Release:** - - Review the draft release - - Update release notes with: - - Feature descriptions - - Bug fixes - - Firmware version information - - PR references - - Publish the release when ready - -### Key Design Features - -- **Separation of Concerns:** Build workflows are separate from artifact staging -- **Branch-Specific Behavior:** Artifacts only uploaded from `dev` branch -- **Reusable Components:** Linux workflows use reusable workflow files for better maintainability -- **Draft Releases:** Releases start as drafts, allowing manual review before publishing -- **Version Extraction:** Version automatically extracted from source code to prevent mismatch -- **Platform Coverage:** Automated builds for Windows and macOS; Linux users build from source -- **Driver Inclusion:** Both platform packages include necessary SiLabs USB drivers - -### Workflow Dependencies - -``` -Push to any branch -├── build-all-on-macos.yml -├── build-all-on-win.yaml -└── linux-workflows.yaml - ├── reusable-workflow-build-oscilloscope-linux.yml - ├── reusable-workflow-build-dataParser-linux.yml - └── reusable-workflow-test-dataParser-linux.yml - -Push to dev branch (after builds complete) -├── upload-build-artifact-mac.yml -└── upload-build-artifact-win.yml - └── Builds Windows Installer - -PR dev → master (when merged) -└── create-draft-release.yml - ├── Downloads EmotiBitSoftware-macos artifact - ├── Downloads EmotiBitSoftware-Windows artifact - └── Creates draft release with all artifacts -``` - -### Notes for Maintainers - -- All workflows use self-hosted runners requiring environment variables for directory paths -- Windows workflow includes special handling for installer version validation -- Linux builds include comprehensive testing of the DataParser component -- The release process assumes artifacts from the `master` branch workflow runs -- Manual workflow dispatch is available for `create-draft-release.yml` for testing diff --git a/dependencies.json b/dependencies.json new file mode 100644 index 00000000..1aaa24c8 --- /dev/null +++ b/dependencies.json @@ -0,0 +1,49 @@ +{ + "dependencies": [ + { + "name": "ofxNetworkUtils", + "repo": "https://github.com/bakercp/ofxNetworkUtils.git", + "ref": "stable" + }, + { + "name": "ofxOscilloscope", + "repo": "https://github.com/produceconsumerobot/ofxOscilloscope.git", + "ref": "master" + }, + { + "name": "ofxThreadedLogger", + "repo": "https://github.com/produceconsumerobot/ofxThreadedLogger.git", + "ref": "master" + }, + { + "name": "ofxBiquadFilter", + "repo": "https://github.com/smukkejohan/ofxBiquadFilter.git", + "ref": "master" + }, + { + "name": "ofxJSON", + "repo": "https://github.com/jeffcrouse/ofxJSON.git", + "ref": "master" + }, + { + "name": "EmotiBit_XPlat_Utils", + "repo": "https://github.com/EmotiBit/EmotiBit_XPlat_Utils.git", + "ref": "master" + }, + { + "name": "ofxLSL", + "repo": "https://github.com/EmotiBit/ofxLSL.git", + "ref": "master" + }, + { + "name": "ofxSerial", + "repo": "https://github.com/EmotiBit/ofxSerial.git", + "ref": "stable" + }, + { + "name": "ofxIO", + "repo": "https://github.com/bakercp/ofxIO.git", + "ref": "stable" + } + ] +} diff --git a/docs/BUILD_AND_RELEASE.md b/docs/BUILD_AND_RELEASE.md new file mode 100644 index 00000000..3f7d023e --- /dev/null +++ b/docs/BUILD_AND_RELEASE.md @@ -0,0 +1,131 @@ +# Build and Release Process + + +## Overview + +The CI/CD pipeline has three stages: + +1. **Build** — triggered on every push to any branch. Builds all apps on all platforms. +2. **Stage** — triggered on pushes to the `dev` branch only. Packages binaries and uploads them as GitHub Actions artifacts. +3. **Release** — triggered when a PR from `dev` → `master` is merged (or manually). Downloads staged artifacts and creates a draft GitHub release. + +``` +Push to any branch +├── build-all-on-macos.yml ──► (dev branch only) stage + upload macOS artifact +├── build-all-on-win.yaml ──► (dev branch only) build installer + upload Windows artifact +└── linux-workflows.yaml + ├── reusable-workflow-build-oscilloscope-linux.yml + ├── reusable-workflow-build-dataParser-linux.yml + ├── reusable-workflow-test-oscilloscope-linux.yml + └── reusable-workflow-test-dataParser-linux.yml + +PR dev → master (merged) +└── create-draft-release.yml + ├── Downloads macOS artifact + ├── Downloads Windows artifact + └── Creates draft GitHub release +``` + +**Version source:** All version strings are read from `src/ofxEmotiBitVersion.h`. + +**Runners:** All builds run on self-hosted runners. Directory paths are configured via repository variables `ADDONS_DIR` and `OFXEMOTIBIT_DIR`. + +--- + +## Applications in the build + +| App | macOS | Windows | Linux | +|-----|-------|---------|-------| +| EmotiBitOscilloscope | Xcode (Release-x86_64) | MSBuild (Release) | make | +| EmotiBitDataParser | Xcode (Release) | MSBuild (Release) | make | +| EmotiBitFirmwareInstaller | Xcode (Release) | MSBuild (Release) | — | +| EmotiBitSlidePlayer | Xcode (Release) | MSBuild (Release) | — | + +Linux builds are CI health-checks only — no release artifacts are produced. + +--- + +## Workflow reference + +### `build-all-on-macos.yml` +**Trigger:** Every push + +1. Clones the repo fresh into the OF addons directory. +2. Builds all four apps in parallel using `xcodebuild`. +3. *(dev branch only)* Stages `.app` bundles and SiLabs VCP drivers into a versioned folder, then uploads the folder as the artifact `EmotiBitSoftware_v{version}-macOS`. + +### `build-all-on-win.yaml` +**Trigger:** Every push + +1. Clones the repo fresh into the OF addons directory. +2. Builds all four apps in parallel using MSBuild. +3. *(dev branch only)* Installs INNO Setup 6 (downloads if not present), downloads the VC++ Redistributable, and compiles `EmotiBitInstaller.iss` into `EmotiBitInstaller-{version}.exe`. +4. *(dev branch only)* Stages the installer and CP210x drivers, then uploads as `EmotiBitSoftware_v{version}-Windows`. + +### `linux-workflows.yaml` +**Trigger:** Every push + +Orchestrates four reusable workflows in sequence: build Oscilloscope → build DataParser → test Oscilloscope → test DataParser. No artifacts are uploaded. + +### `reusable-workflow-build-oscilloscope-linux.yml` / `reusable-workflow-build-dataParser-linux.yml` +Called by `linux-workflows.yaml`. Runs `make -j` in the respective app directory on the self-hosted Linux runner. + +### `reusable-workflow-test-oscilloscope-linux.yml` +Runs the auxiliary control automation test script for the Oscilloscope. + +### `reusable-workflow-test-dataParser-linux.yml` +Runs five DataParser test suites: parsed data hash validation, unused typetag detection, output format validation, composite payload parsing, and timesync response parsing. + +### `create-draft-release.yml` +**Trigger:** PR from `dev` → `master` merged, or manual dispatch + +1. Extracts version from `src/ofxEmotiBitVersion.h`. +2. Downloads the macOS and Windows artifacts from the `dev` branch runs. +3. Generates a release notes template and a sample `config.txt` (WiFi credentials). +4. Creates a **draft** GitHub release tagged `v{version}` with all artifacts attached. + +The draft must be reviewed and published manually. + +### `validate-version-linux.yml` +**Trigger:** Every push + +Checks that the version in `ofxEmotiBitVersion.h` is formatted as `X.Y.Z` and is greater than the version on `dev`. Fails the build if the version is lower. + +### `check-installer-project-version.yml` +**Deprecated.** Kept for reference. Previously validated the Visual Studio installer project version; replaced by INNO Setup which reads the version automatically. + +### `test-powershell.yml` +**Trigger:** Manual only + +Validates that the Windows runner has PowerShell, INNO Setup 6, and the VC++ Redistributable available. Useful for debugging runner environment issues. + +--- + +## Updating the workflows + +### Adding a new application + +**macOS — `build-all-on-macos.yml`:** +1. Add a new build job (same structure as the existing ones, depending on `clone-macos`). +2. In the `upload-artifact-macos` job, add a `mv` command to move the new `.app` bundle into the staging folder. + +**Windows — `build-all-on-win.yaml`:** +1. Add a new build job (same structure as the existing ones, depending on `clone-windows`). +2. Add the new app's output binary to `EmotiBitInstaller.iss` so it is included in the installer. +3. The `build-installer-windows` and `upload-artifact-windows` jobs do not need changes — the installer bundles everything defined in the `.iss` file. + +**Linux — `linux-workflows.yaml` + new reusable workflow files:** +1. Create `reusable-workflow-build-{appname}-linux.yml` (copy an existing one, update the directory and make target). +2. Optionally create `reusable-workflow-test-{appname}-linux.yml` if tests exist. +3. Add the corresponding `uses:` call(s) in `linux-workflows.yaml`. + +### Updating distributable files (e.g. new drivers, bundled assets) + +**macOS artifact (`build-all-on-macos.yml` → `upload-artifact-macos` job):** +Add a `cp -r` command in the staging step to copy the new files into the staging folder before the upload step. + +**Windows installer (`EmotiBitInstaller.iss`):** +Add the new files under the `[Files]` section of `EmotiBitInstaller.iss`. The workflow compiles this file as-is, so no workflow change is needed — only the `.iss` file. + +**Linux:** +No release artifacts are produced on Linux; no changes needed. diff --git a/scripts/download-dependencies.sh b/scripts/download-dependencies.sh new file mode 100755 index 00000000..d584c48f --- /dev/null +++ b/scripts/download-dependencies.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash +# download-dependencies.sh +# +# Usage: +# bash scripts/download-dependencies.sh # clone all deps +# bash scripts/download-dependencies.sh --cleanup # remove all dep clones +# +# Reads dependencies.json from the repo root. Each dependency is cloned into +# ADDONS_DIR at the specified ref (branch, tag, or commit SHA), mirroring how +# ofxEmotiBit itself is checked out and placed in ADDONS_DIR by the CI workflow. +# +# Phase 2: enforces the manifest. Run before record-dependencies.sh so the +# report reflects exactly what was downloaded. + +set -euo pipefail + +# --- argument parsing --- +CLEANUP=false +if [ "$1" = "--cleanup" ]; then + CLEANUP=true + ADDONS_DIR="$2" +else + ADDONS_DIR="$1" +fi + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(dirname "$SCRIPT_DIR")" +# On Windows, Git Bash uses MSYS2 paths (/c/...) but native Windows Python +# requires Windows-style paths (C:/...). pwd -W returns the Windows-style +# path in Git Bash; on macOS/Linux it is unrecognized and we fall back to pwd. +REPO_ROOT_PY="$(cd "$REPO_ROOT" && pwd -W 2>/dev/null || pwd)" +DEPS_JSON="$REPO_ROOT/dependencies.json" +DEPS_JSON_PY="$REPO_ROOT_PY/dependencies.json" + +if [ ! -f "$DEPS_JSON" ]; then + echo "ERROR: dependencies.json not found at $DEPS_JSON" + exit 1 +fi + +# --- cleanup mode: remove all dep directories --- +if [ "$CLEANUP" = true ]; then + echo "Cleaning up cloned dependencies from $ADDONS_DIR" + python3 -c " +import json +with open('$DEPS_JSON_PY') as f: + for d in json.load(f)['dependencies']: + print(d['name']) +" | tr -d '\r' | while read -r name; do + target="$ADDONS_DIR/$name" + if [ -d "$target" ]; then + echo " removing $name" + rm -rf "$target" + fi + done + echo "Cleanup complete." + exit 0 +fi + +# --- download mode: clone each dep at specified ref --- +echo "Downloading dependencies into $ADDONS_DIR" + +python3 -c " +import json +with open('$DEPS_JSON_PY') as f: + for d in json.load(f)['dependencies']: + print(d['name'] + ' ' + d['repo'] + ' ' + d['ref']) +" | tr -d '\r' | while IFS=' ' read -r name repo ref; do + target="$ADDONS_DIR/$name" + + if [ -d "$target" ]; then + echo "==> $name: removing existing clone" + rm -rf "$target" + fi + + echo "==> $name: cloning $repo at ref '$ref'" + git clone "$repo" "$target" + git -C "$target" checkout "$ref" + echo " resolved: $(git -C "$target" rev-parse HEAD)" +done + +echo "" +echo "All dependencies downloaded." diff --git a/scripts/record-dependencies.sh b/scripts/record-dependencies.sh new file mode 100755 index 00000000..88e5c887 --- /dev/null +++ b/scripts/record-dependencies.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +# record-dependencies.sh +# +# Usage: bash scripts/record-dependencies.sh +# +# Reads dependencies.json from the repo root, inspects each listed addon +# directory that is already present on the runner, and records its actual +# HEAD commit SHA and ref into a Markdown report file: +# dependencies-report-.txt +# +# Does NOT clone or modify any addon directory (Phase 1 — logging only). +# Run this immediately after checking out ofxEmotiBit in CI so the record +# is written before any build starts. + +set -euo pipefail + +if [ $# -lt 2 ]; then + echo "Usage: $0 " + exit 1 +fi + +ADDONS_DIR="$1" +PLATFORM="$2" + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(dirname "$SCRIPT_DIR")" +# On Windows, Git Bash uses MSYS2 paths (/c/...) but native Windows Python +# requires Windows-style paths (C:/...). pwd -W returns the Windows-style +# path in Git Bash; on macOS/Linux it is unrecognized and we fall back to pwd. +REPO_ROOT_PY="$(cd "$REPO_ROOT" && pwd -W 2>/dev/null || pwd)" +DEPS_JSON="$REPO_ROOT/dependencies.json" +DEPS_JSON_PY="$REPO_ROOT_PY/dependencies.json" +REPORT="$REPO_ROOT/dependencies-report-${PLATFORM}.txt" + +if [ ! -f "$DEPS_JSON" ]; then + echo "ERROR: dependencies.json not found at $DEPS_JSON" + exit 1 +fi + +OFXEMOTIBIT_COMMIT=$(git -C "$REPO_ROOT" rev-parse HEAD) +OFXEMOTIBIT_REF=$(git -C "$REPO_ROOT" symbolic-ref --short HEAD 2>/dev/null || echo "(detached)") + +cat > "$REPORT" </dev/null \ + || git -C "$target" describe --tags --exact-match HEAD 2>/dev/null \ + || echo "(detached)") + else + resolved_commit="NOT FOUND" + resolved_ref="NOT FOUND" + fi + echo "| $name | \`$ref\` | \`$resolved_commit\` | \`$resolved_ref\` |" >> "$REPORT" +done + +echo "" +echo "Dependency report written to: $REPORT" +echo "" +cat "$REPORT" diff --git a/src/DoubleBuffer.h b/src/DoubleBuffer.h index 88c668ab..43639eff 100644 --- a/src/DoubleBuffer.h +++ b/src/DoubleBuffer.h @@ -34,13 +34,13 @@ class DoubleBuffer { // assignment operator DoubleBuffer& operator=(const DoubleBuffer &that) { - std::lock_guard guard(that->_threadlock); - if (this != that) { + std::lock_guard guard(that._threadlock); + if (this != &that) { std::lock_guard guard(_threadlock); - _buffer0 = that->_buffer0; - _buffer1 = that->_buffer1; - _inputPtr = _buffer0.data(); - _outputPtr = _buffer1.data(); + _buffer0 = that._buffer0; + _buffer1 = that._buffer1; + _inputPtr = &_buffer0; + _outputPtr = &_buffer1; } return *this; } diff --git a/src/ofxEmotiBitVersion.h b/src/ofxEmotiBitVersion.h index 4a56a9fa..95b61d56 100644 --- a/src/ofxEmotiBitVersion.h +++ b/src/ofxEmotiBitVersion.h @@ -2,16 +2,15 @@ //#include #include "ofMain.h" - -const std::string ofxEmotiBitVersion = "1.14.7"; +const std::string ofxEmotiBitVersion = "1.16.5"; static const char SOFTWARE_VERSION_PREFIX = 'v'; static void writeOfxEmotiBitVersionFile() { string filename = "ofxEmotiBit_Version.txt"; remove(ofToDataPath(filename).c_str()); - ofstream mFile; - mFile.open(ofToDataPath(filename).c_str(), ios::out); + std::ofstream mFile; + mFile.open(ofToDataPath(filename).c_str(), std::ios::out); mFile << ofxEmotiBitVersion.c_str(); mFile.close(); }