Skip to content

Commit

Permalink
add pybind11 bridge
Browse files Browse the repository at this point in the history
  • Loading branch information
charlesneimog committed Jul 7, 2024
1 parent e22b9df commit 6921875
Show file tree
Hide file tree
Showing 20 changed files with 510 additions and 227 deletions.
19 changes: 11 additions & 8 deletions .github/workflows/c-cpp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,25 +26,27 @@ jobs:
arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
arch -x86_64 /usr/local/bin/brew install pd
arch -x86_64 /usr/local/bin/brew install boost
arch -x86_64 /usr/local/bin/brew install pybind11
- name: Install PureData and Deps arm64 Mac
if: ${{ matrix.arch == 'arm64' }}
run: |
brew install pd
brew install boost
brew install pybind11
- name: Build Object for Arm
if: ${{ matrix.arch == 'arm64' }}
run: |
export CPLUS_INCLUDE_PATH="$CPLUS_INCLUDE_PATH:/opt/homebrew/include/"
export LDFLAGS="-L/opt/homebrew/lib"
cmake . -B build -DCMAKE_OSX_ARCHITECTURES=arm64 -DPD_FLOATSIZE=${{ matrix.precision }} -DPDLIBDIR=./
cmake . -B build -DCMAKE_OSX_ARCHITECTURES=arm64 -DPD_FLOATSIZE=${{ matrix.precision }} -DPDLIBDIR=./ -DBUILD_ALL=ON
cmake --build build -j $(sysctl -n hw.logicalcpu)
make install -C build
- name: Build Object for x86_64
if: ${{ matrix.arch == 'amd64' }}
run: |
export CPLUS_INCLUDE_PATH="$CPLUS_INCLUDE_PATH:/usr/local/include/"
export LDFLAGS="-L/usr/local/lib"
cmake . -B build -DPD_FLOATSIZE=${{ matrix.precision }} -DCMAKE_OSX_ARCHITECTURES=x86_64 -DPDLIBDIR=./
cmake . -B build -DPD_FLOATSIZE=${{ matrix.precision }} -DCMAKE_OSX_ARCHITECTURES=x86_64 -DPDLIBDIR=./ -DBUILD_ALL=ON
cmake --build build -j $(sysctl -n hw.logicalcpu)
make install -C build
- name: Upload Object
Expand All @@ -71,7 +73,7 @@ jobs:
with:
msystem: mingw64
update: false
install: make mingw-w64-x86_64-gcc mingw64/mingw-w64-x86_64-cmake mingw-w64-x86_64-boost
install: make mingw-w64-x86_64-gcc mingw64/mingw-w64-x86_64-cmake mingw-w64-x86_64-boost mingw-w64-x86_64-pybind11
- name: Install winget
uses: Cyberboss/install-winget@v1
- name: Install PureData Float 32
Expand All @@ -85,14 +87,14 @@ jobs:
- name: Configure and build Visual Studio
if: matrix.compiler == 'msvc'
run: |
cmake . -B build -DPD_FLOATSIZE=${{ matrix.precision }} -DPDLIBDIR=./
cmake . -B build -DPD_FLOATSIZE=${{ matrix.precision }} -DPDLIBDIR=./ -DBUILD_ALL=ON
cmake --build build
cmake --install build
- name: Configure and build Mingw
shell: msys2 {0}
if: matrix.compiler == 'mingw'
run: |
cmake . -B build -DPD_FLOATSIZE=${{ matrix.precision }} -DPDLIBDIR=./
cmake . -B build -DPD_FLOATSIZE=${{ matrix.precision }} -DPDLIBDIR=./ -DBUILD_ALL=ON
cmake --build build
cmake --install build
- name: Upload
Expand All @@ -117,6 +119,7 @@ jobs:
sudo add-apt-repository ppa:pure-data/pure-data -y
sudo apt install puredata -y
sudo apt install libboost-all-dev
sudo apt install python3-pybind11 -y
- name: Install aarch64 gcc
if: matrix.arch == 'aarch64'
run: |
Expand All @@ -130,19 +133,19 @@ jobs:
- name: Build Object
if: matrix.arch == 'amd64'
run: |
cmake . -B build -DPD_FLOATSIZE=${{ matrix.precision }} -DPDLIBDIR=./
cmake . -B build -DPD_FLOATSIZE=${{ matrix.precision }} -DPDLIBDIR=./ -DBUILD_ALL=ON
cmake --build build -- -j$(nproc)
make install -C build
- name: Build Object
if: matrix.arch == 'aarch64'
run: |
cmake . -B build -DPD_FLOATSIZE=${{ matrix.precision }} -DCMAKE_SYSTEM_PROCESSOR=aarch64 -DPDLIBDIR=./
cmake . -B build -DPD_FLOATSIZE=${{ matrix.precision }} -DCMAKE_SYSTEM_PROCESSOR=aarch64 -DPDLIBDIR=./ -DBUILD_ALL=ON
cmake --build build -- -j$(nproc)
make install -C build
- name: Build Object
if: matrix.arch == 'arm'
run: |
cmake . -B build -DPD_FLOATSIZE=${{ matrix.precision }} -DCMAKE_SYSTEM_PROCESSOR=arm -DPDLIBDIR=./
cmake . -B build -DPD_FLOATSIZE=${{ matrix.precision }} -DCMAKE_SYSTEM_PROCESSOR=arm -DPDLIBDIR=./ -DBUILD_ALL=ON
cmake --build build -- -j$(nproc)
make install -C build
- name: Upload Object
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,6 @@ Resources/Research/__pycache__
Tests/.mscbackup
Tests/__pycache__
.cmake-format.yaml
dist
poetry.lock
Sources/Python/OScofo.egg-info
65 changes: 54 additions & 11 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,21 @@ cmake_minimum_required(VERSION 3.20)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

project(OScofo LANGUAGES CXX)

set(PDCMAKE_DIR
"${CMAKE_CURRENT_SOURCE_DIR}/Resources/pd.cmake/"
CACHE PATH "Path to pd.cmake")
include(${PDCMAKE_DIR}/pd.cmake)

project(o.scofo~)
option(BUILD_PY_MODULE "Build Python Module" OFF)
option(BUILD_PD_OBJECT "Build PureData Object" OFF)
option(BUILD_ALL "Build Python Module and PureData Object" OFF)

if(BUILD_ALL)
set(BUILD_PY_MODULE ON)
set(BUILD_PD_OBJECT ON)
endif()

# ╭──────────────────────────────────────╮
# │ FFTW3 │
Expand All @@ -28,18 +37,52 @@ set_target_properties(fftw3 PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_D
set_target_properties(fftw3 PROPERTIES POSITION_INDEPENDENT_CODE ON)

# ╭──────────────────────────────────────╮
#Library
# OScofo
# ╰──────────────────────────────────────╯
file(GLOB SCOFO_SRC "${CMAKE_CURRENT_SOURCE_DIR}/Sources/*.cpp")
pd_add_external(o.scofo~ "${SCOFO_SRC}" TARGET oscofo)
target_include_directories(oscofo PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/Libraries/fftw-3.3.10/api")
target_link_libraries(oscofo PUBLIC fftw3)
target_compile_options(oscofo PRIVATE -static-libstdc++ -static-libgcc -Wl,-allow-multiple-definition)
target_link_options(oscofo PRIVATE -static-libstdc++ -static-libgcc -Wl,-allow-multiple-definition)
list(APPEND SCofoSrc Sources/OScofo/mdp.cpp)
list(APPEND SCofoSrc Sources/OScofo/mir.cpp)
list(APPEND SCofoSrc Sources/OScofo/score.cpp)
list(APPEND SCofoSrc Sources/OScofo/OScofo.cpp)
add_library(OScofo STATIC ${SCofoSrc})
target_link_libraries(OScofo PUBLIC fftw3)
set_target_properties(OScofo PROPERTIES POSITION_INDEPENDENT_CODE ON)
target_include_directories(OScofo PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/Libraries/fftw-3.3.10/api")

# ╭──────────────────────────────────────╮
# │ Python Module │
# ╰──────────────────────────────────────╯
if(BUILD_PY_MODULE)
find_package(Python REQUIRED COMPONENTS Interpreter Development)
find_package(pybind11 REQUIRED)
include_directories(${PYTHON_INCLUDE_DIRS})
include_directories(${pybind11_INCLUDE_DIR})
pybind11_add_module(_OScofo Sources/Python/PyOScofo.cpp)
target_link_libraries(_OScofo PRIVATE OScofo ${PYTHON_LIBRARIES})
set_target_properties(
_OScofo
PROPERTIES CXX_STANDARD 11
CXX_STANDARD_REQUIRED YES
CXX_EXTENSIONS NO)
if(NOT APPLE)
target_compile_options(_OScofo PRIVATE -static-libstdc++ -static-libgcc -Wl,-allow-multiple-definition)
target_link_options(_OScofo PRIVATE -static-libstdc++ -static-libgcc -Wl,-allow-multiple-definition)
endif()
install(TARGETS _OScofo DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/Sources/Python/OScofo/)
endif()

# ╭──────────────────────────────────────╮
# Data Files
#PureData Object
# ╰──────────────────────────────────────╯
pd_add_datafile(oscofo "${CMAKE_CURRENT_SOURCE_DIR}/README.md")
pd_add_datafile(oscofo "${CMAKE_CURRENT_SOURCE_DIR}/Resources/o.scofo~-help.pd")
if(BUILD_PD_OBJECT)
project(o.scofo~)
pd_add_external(o.scofo~ Sources/PureData/o.scofo~.cpp TARGET PdOScofo)
target_include_directories(PdOScofo PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/Libraries/fftw-3.3.10/api")
if(NOT APPLE)
target_compile_options(PdOScofo PRIVATE -static-libstdc++ -static-libgcc -Wl,-allow-multiple-definition)
target_link_options(PdOScofo PRIVATE -static-libstdc++ -static-libgcc -Wl,-allow-multiple-definition)
endif()
target_link_libraries(PdOScofo PRIVATE OScofo)

pd_add_datafile(pdOScofo "${CMAKE_CURRENT_SOURCE_DIR}/README.md")
pd_add_datafile(pdOScofo "${CMAKE_CURRENT_SOURCE_DIR}/Resources/o.scofo~-help.pd")
endif()
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
# o.scofo~
# OScofo

**O**pen **SCO**re **FO**llower (o.scofo~) is a PureData object designed for contemporary music applications. This project aims to encourage collaboration among researchers and musicians for contemporary music. Here's what you need to know:
**O**pen **SCO**re **FO**llower (OScofo) is a PureData object designed for contemporary music applications. This project aims to encourage collaboration among researchers and musicians for contemporary music. Here's what you need to know:

## Goal

The goal of *o.scofo~* is to provide a simple, accessible tool for real-time score following. By keeping the software lightweight, it can be easily run on the web using the [pd4web](https://charlesneimog.github.io/pd4web/) platform, now the we can use PureData on Web Browsers. So finally we have something that can be run for rehearsals one click distance (without externals, OSs incompatibility, etc).
The goal of *OScofo* is to provide a simple, accessible tool for real-time score following. By keeping the software lightweight, it can be easily run on the web using the [pd4web](https://charlesneimog.github.io/pd4web/) platform, now the we can use PureData on Web Browsers. So finally we have something that can be run for rehearsals one click distance (without externals, OSs incompatibility, etc).

## Collaboration and Contribution

I invite researchers and developers to contribute to the *o.scofo~* project. By sharing the source code, I am trying to provide access to the theories and mathematical formulas that drive the software. This transparency is inspired by the amazing tools like IEM, SPARTA, and of course, PureData. My main aim is artistic, maybe your research can help me and a lot of others composers.
I invite researchers and developers to contribute to the *OScofo* project. By sharing the source code, I am trying to provide access to the theories and mathematical formulas that drive the software. This transparency is inspired by the amazing tools like IEM, SPARTA, and of course, PureData. My main aim is artistic, maybe your research can help me and a lot of others composers.

## Technical Foundations

*o.scofo~* uses several concepts developed by many researches:
*OScofo* uses several concepts developed by many researches:

* **Score Language**: Based on the `scofo` (by Miller Puckette) and `antescofo~` (by Arshia Cont) language.
* **Pitch Comparison**: Utilizes the Kullback-Leibler (KL) Divergence method for pitch comparison as presented by Arshia Cont in 2008 and 2010.
* **Rhythm Synchronization**: Integrates theories of rhythm synchronization developed by Edward Large and Mari Riess Jones (1999) and Edward Large and Caroline Palmer (2002), as presented for Cont (2010).
* **Descriptors**: Employs descriptors from Willian Brent's timbreIDLib project for analyzing and identifying timbral characteristics.

40 changes: 40 additions & 0 deletions Sources/OScofo.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,43 @@
#include "OScofo/mir.hpp"
#include "OScofo/score.hpp"
#include "OScofo/states.hpp"

#define OSCOFO_VERSION_MAJOR 0
#define OSCOFO_VERSION_MINOR 1
#define OSCOFO_VERSION_PATCH 0

class OScofo {
public:
OScofo(float Sr, float WindowSize, float HopSize);

// Set Functions
void SetPitchTemplateSigma(double Sigma);
void SetHarmonics(int Harmonics);
void SetTimeAccumFactor(double TimeAccumFactor);
void SetTimeCouplingStrength(double TimeCouplingStrength);
void SetdBTreshold(double dB);
void SetTunning(double Tunning);
void SetCurrentEvent(int Event);

// Get Functions
float GetLiveBPM();
int GetEventIndex();

// Main Functions
bool ParseScore(std::string ScorePath);
bool ProcessBlock(std::vector<double> &AudioBuffer);

// Helpers Functions
bool ScoreIsLoaded();
std::string GetError();

private:
OScofoMDP m_MDP;
OScofoMIR m_MIR;
OScofoScore m_Score;

States m_States;
Description m_Desc;

std::string m_Error;
};
108 changes: 103 additions & 5 deletions Sources/OScofo/OScofo.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,104 @@
#pragma once
#include "../OScofo.hpp"

#include "OScofo/mdp.hpp"
#include "OScofo/mir.hpp"
#include "OScofo/score.hpp"
#include "OScofo/states.hpp"
// ─────────────────────────────────────
OScofo::OScofo(float Sr, float WindowSize, float HopSize)
: m_MDP(Sr, WindowSize, HopSize), m_MIR(Sr, WindowSize, HopSize) {
m_States = States();
m_Desc = Description();
}

// ╭─────────────────────────────────────╮
// │ Set Functions │
// ╰─────────────────────────────────────╯
void OScofo::SetPitchTemplateSigma(double Sigma) {
m_MDP.SetPitchTemplateSigma(Sigma);
m_MDP.UpdatePitchTemplate();
}

// ─────────────────────────────────────
void OScofo::SetHarmonics(int Harmonics) {
m_MDP.SetHarmonics(Harmonics);
m_MDP.UpdatePitchTemplate();
}

// ─────────────────────────────────────
void OScofo::SetTimeCouplingStrength(double TimeCouplingStrength) {
m_MDP.SetTimeCouplingStrength(TimeCouplingStrength);
}

// ─────────────────────────────────────
void OScofo::SetTimeAccumFactor(double TimeAccumFactor) {
m_MDP.SetTimeAccumFactor(TimeAccumFactor);
}

// ─────────────────────────────────────
void OScofo::SetdBTreshold(double dB) {
m_MDP.SetdBTreshold(dB);
}

// ─────────────────────────────────────
void OScofo::SetTunning(double Tunning) {
m_MDP.SetTunning(Tunning);
m_Score.SetTunning(Tunning);
}

// ─────────────────────────────────────
void OScofo::SetCurrentEvent(int Event) {
m_MDP.SetCurrentEvent(Event);
}

// ╭─────────────────────────────────────╮
// │ Get Functions │
// ╰─────────────────────────────────────╯
int OScofo::GetEventIndex() {
return 0; // TODO: Implement yet
}

// ─────────────────────────────────────
std::string OScofo::GetError() {
return m_Error;
}

// ─────────────────────────────────────
float OScofo::GetLiveBPM() {
return m_MDP.GetLiveBPM();
}

// ╭─────────────────────────────────────╮
// │ Helpers Functions │
// ╰─────────────────────────────────────╯
bool OScofo::ScoreIsLoaded() {
return m_Score.ScoreIsLoaded();
}

// ╭─────────────────────────────────────╮
// │ Main Functions │
// ╰─────────────────────────────────────╯
bool OScofo::ParseScore(std::string ScorePath) {
LOGE() << "OScofo::ParseScore";
m_Score.Parse(m_States, ScorePath);
LOGE() << "Score has " << m_States.size() << " states";

for (int i = 0; i < m_States.size(); i++) {
if (!m_States[i].Valid) {
m_Error = std::string("Error on line ") + std::to_string(m_States[i].Line) + ": " +
m_States[i].Error;
return false;
}
}
m_MDP.SetScoreStates(m_States);
m_MDP.UpdatePhaseValues();
return true;
}

// ─────────────────────────────────────
bool OScofo::ProcessBlock(std::vector<double> &AudioBuffer) {
if (!m_Score.ScoreIsLoaded()) {
return true;
}

m_MIR.GetDescription(AudioBuffer, m_Desc);
// m_MDP.GetEvent(m_Desc);

return true;
}
4 changes: 2 additions & 2 deletions Sources/OScofo/log.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
#include <iostream>
#include <sstream>

#define SCOFO_DEBUG true
#define SCOFO_DEBUG false

#if SCOFO_DEBUG
class LogStream {
public:
template <typename T> LogStream &operator<<(const T &value) {
Expand All @@ -24,6 +23,7 @@ class LogStream {
std::ostringstream buffer;
};

#if SCOFO_DEBUG
#define LOGE() LogStream()
#else
#define LOGE() \
Expand Down
Loading

0 comments on commit 6921875

Please sign in to comment.