├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── examples ├── audio │ ├── CMakeLists.txt │ ├── LiveAudio.h │ ├── MyLiveAudio.h │ └── main.cpp ├── audioweb │ ├── .gitignore │ ├── AppDelegate.h │ ├── AppDelegate.mm │ ├── Biquad.h │ ├── CMakeLists.txt │ ├── LiveAudioWeb.h │ ├── MyLiveAudioWeb.h │ ├── buildWeb.sh │ ├── emscripten │ │ ├── audio.js │ │ └── em.cpp │ ├── favicon.png │ ├── index.html │ └── main.mm ├── openFrameworks │ ├── README.md │ ├── main.cpp │ ├── ofApp.cpp │ └── ofApp.h ├── simple │ ├── CMakeLists.txt │ ├── LiveApp.h │ ├── MyLiveApp.h │ └── main.cpp └── tetris │ ├── CMakeLists.txt │ ├── LiveAudio.h │ ├── MyLiveAudio.h │ └── main.cpp └── src ├── CMakeLists.txt ├── Dylib.cpp ├── Dylib.h ├── FileWatcher.cpp ├── FileWatcher.h ├── ReloadableClass.h ├── liveCodeUtils.cpp ├── liveCodeUtils.h ├── ofxCppSketch.cpp ├── ofxCppSketch.h ├── ofxCppSketchSoundStream.cpp └── ofxCppSketchSoundStream.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Compiled Static libraries 20 | *.lai 21 | *.la 22 | *.a 23 | *.lib 24 | 25 | # Executables 26 | *.exe 27 | *.out 28 | *.app 29 | 30 | .DS_Store 31 | examples/simple/simple 32 | examples/audio/audio 33 | examples/tetris/tetris 34 | .vscode/settings.json 35 | build -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ext/rtaudio"] 2 | path = ext/rtaudio 3 | url = https://github.com/thestk/rtaudio.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019, elf audio 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cppsketch 2 | 3 | Simple system for hot-reloading C++ classes in order to sketch ideas and algorithms. It's quite naive, in that it just starts your program again and doesn't save variables. Also, at the moment, it only works for one class at a time. 4 | 5 | (Mac only for now) 6 | Here's a video playlist on YouTube for how to use it: https://www.youtube.com/watch?v=-JlMIAOluUg&list=PL3QE2n0UDmRp2R7x-nB5dz1BPHVuBuKZS 7 | It has no dependencies - everything is included in here. (except if you want to run the audio example, see below) 8 | 9 | ## VIDEO 10 | 11 | The best documentation is the 3 minute video on youtube here: https://www.youtube.com/watch?v=-JlMIAOluUg 12 | 13 | ## How to compile 14 | 15 | cd to examples/simple and type: 16 | 17 | ``` 18 | cmake -Bbuild -GNinja 19 | cmake --build build --config Release 20 | ``` 21 | 22 | ## How to run 23 | 24 | type `./simple` - now you can edit MyLiveApp.h live. 25 | 26 | ## Audio example 27 | 28 | (there's a 2 minute video tutorial of how to do this here: https://www.youtube.com/watch?v=hVUjeGlL7Y8) 29 | 30 | To compile this example, do the following 31 | 32 | 1. `cd` to the cppsketch repository 33 | 2. type `git submodule init` followed by `git submodule update` - this gets the rtAudio 34 | 3. `cd examples/audio` 35 | 4. follow instructions for "how to compile" above 36 | 37 | To run it, type `./audio` - now you can edit MyLiveAudio.h live, and every time you save it should update. 38 | 39 | ## openFrameworks 40 | 41 | Please see the readme in the examples/openFrameworks directory to see how to do that, or go here for an addon https://github.com/elf-audio/ofxCppSketch (probably easier) 42 | 43 | ## linux / raspberry pi 44 | 45 | At the moment, only the simple and audio examples work. You may need to install libasound2-dev. You can do this by typing `sudo apt install libasound2-dev` 46 | -------------------------------------------------------------------------------- /examples/audio/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | cmake_minimum_required(VERSION 3.18) 4 | 5 | set(APP_NAME audio) 6 | project(${APP_NAME} VERSION 1.0.0) 7 | 8 | set(CMAKE_CXX_STANDARD 20) 9 | set(CMAKE_CXX_STANDARD_REQUIRED True) 10 | 11 | # Add main.cpp (or other source files as needed) 12 | add_executable(${APP_NAME} 13 | main.cpp 14 | ) 15 | 16 | # Add the src subdirectory 17 | add_subdirectory(../../src cppsketch) 18 | add_subdirectory(../../ext/rtaudio rtaudio) 19 | # Add the include directory 20 | target_include_directories(${APP_NAME} PRIVATE ../../ext/rtaudio) 21 | # Link the executable with the src_lib 22 | target_link_libraries(${APP_NAME} cppsketch_lib rtaudio) 23 | 24 | # OS-specific options 25 | if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") 26 | # Add -ldl for Linux 27 | target_link_libraries(${APP_NAME} ${CMAKE_DL_LIBS}) 28 | endif() 29 | 30 | set_target_properties(${APP_NAME} PROPERTIES 31 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" 32 | ) 33 | -------------------------------------------------------------------------------- /examples/audio/LiveAudio.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | class LiveAudio { 4 | public: 5 | virtual void setup() {} 6 | virtual void audioOut(float *samples, int length, int numChans) {} 7 | }; 8 | 9 | -------------------------------------------------------------------------------- /examples/audio/MyLiveAudio.h: -------------------------------------------------------------------------------- 1 | #include "LiveAudio.h" 2 | 3 | class MyLiveAudio : public LiveAudio { 4 | public: 5 | 6 | float phase = 0; 7 | float lfo = 0; 8 | 9 | void audioOut(float *samples, int length, int numChans) override { 10 | for(int i = 0; i < length; i++) { 11 | samples[i*2] = samples[i*2+1] = phase * 0.2; 12 | phase += 0.01 + lfo*0.001; 13 | if(phase>1.f) phase -= 2.f; 14 | lfo += 0.000005; 15 | if(lfo>1.f) lfo -= 2.f; 16 | } 17 | } 18 | }; -------------------------------------------------------------------------------- /examples/audio/main.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include 4 | #include "ReloadableClass.h" 5 | #include "LiveAudio.h" 6 | #include "RtAudio.h" 7 | #include 8 | 9 | LiveAudio *audio = nullptr; 10 | 11 | std::mutex audioMutex; 12 | 13 | // Two-channel sawtooth wave generator. 14 | int audioOutCallback(void *outputBuffer, 15 | void *inputBuffer, 16 | unsigned int nBufferFrames, 17 | double streamTime, 18 | RtAudioStreamStatus status, 19 | void *userData) { 20 | float *output = (float *) outputBuffer; 21 | audioMutex.lock(); 22 | if (audio) { 23 | audio->audioOut(output, nBufferFrames, 2); 24 | } else { 25 | memset(output, 0, nBufferFrames * 2 * sizeof(float)); 26 | } 27 | audioMutex.unlock(); 28 | return 0; 29 | } 30 | 31 | RtAudio rtAudio; 32 | 33 | void startRtAudio() { 34 | RtAudio::StreamParameters parameters; 35 | parameters.deviceId = rtAudio.getDefaultOutputDevice(); 36 | parameters.nChannels = 2; 37 | parameters.firstChannel = 0; 38 | unsigned int bufferFrames = 256; // 256 sample frames 39 | 40 | try { 41 | rtAudio.openStream(¶meters, NULL, RTAUDIO_FLOAT32, 44100, &bufferFrames, &audioOutCallback, nullptr); 42 | rtAudio.startStream(); 43 | } catch (RtAudioError &e) { 44 | e.printMessage(); 45 | exit(0); 46 | } 47 | } 48 | 49 | int main(int argc, char *argv[]) { 50 | ReloadableClass dylib; 51 | 52 | dylib.willCloseDylib = []() { 53 | audioMutex.lock(); 54 | audio = nullptr; 55 | audioMutex.unlock(); 56 | }; 57 | dylib.reloaded = [](LiveAudio *ptr) { 58 | audioMutex.lock(); 59 | audio = ptr; 60 | audio->setup(); 61 | audioMutex.unlock(); 62 | }; 63 | 64 | dylib.init("MyLiveAudio.h"); 65 | 66 | startRtAudio(); 67 | 68 | while (1) { 69 | dylib.checkForChanges(); 70 | 71 | usleep(1000 * 1000); 72 | } 73 | 74 | return 0; 75 | } 76 | -------------------------------------------------------------------------------- /examples/audioweb/.gitignore: -------------------------------------------------------------------------------- 1 | audioweb 2 | emscripten/*.wasm 3 | emscripten/em.js -------------------------------------------------------------------------------- /examples/audioweb/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // wk 4 | // 5 | // Created by Marek Bereza on 19/11/2020. 6 | // 7 | 8 | #import 9 | #import 10 | @interface AppDelegate : NSObject 11 | 12 | 13 | @end 14 | 15 | -------------------------------------------------------------------------------- /examples/audioweb/AppDelegate.mm: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // wk 4 | // 5 | // Created by Marek Bereza on 19/11/2020. 6 | // 7 | 8 | #import "AppDelegate.h" 9 | #import 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | using namespace std; 16 | 17 | 18 | 19 | #include 20 | #include "ReloadableClass.h" 21 | #include "LiveAudioWeb.h" 22 | #include "RtAudio.h" 23 | #include 24 | #import "AppDelegate.h" 25 | 26 | LiveAudioWeb *audio = nullptr; 27 | ReloadableClass dylib; 28 | 29 | mutex audioMutex; 30 | 31 | // Two-channel sawtooth wave generator. 32 | int audioOutCallback( void *outputBuffer, void *inputBuffer, unsigned int nBufferFrames, 33 | double streamTime, RtAudioStreamStatus status, void *userData) { 34 | float *output = (float*) outputBuffer; 35 | audioMutex.lock(); 36 | if(audio) { 37 | 38 | audio->audioOut(output, nBufferFrames, 2); 39 | } else { 40 | 41 | memset(output, 0, nBufferFrames * 2 * sizeof(float)); 42 | } 43 | audioMutex.unlock(); 44 | return 0; 45 | } 46 | 47 | 48 | RtAudio rtAudio; 49 | 50 | void startRtAudio() { 51 | RtAudio::StreamParameters parameters; 52 | parameters.deviceId = rtAudio.getDefaultOutputDevice(); 53 | parameters.nChannels = 2; 54 | parameters.firstChannel = 0; 55 | unsigned int bufferFrames = 256; // 256 sample frames 56 | 57 | try { 58 | rtAudio.openStream( ¶meters, NULL, RTAUDIO_FLOAT32, 44100, &bufferFrames, &audioOutCallback, nullptr ); 59 | rtAudio.startStream(); 60 | } catch ( RtAudioError& e ) { 61 | e.printMessage(); 62 | exit( 0 ); 63 | } 64 | } 65 | 66 | 67 | 68 | 69 | @interface LambdaMenuItem : NSMenuItem { 70 | function actionLambda; 71 | } 72 | 73 | - (id)initWithTitle:(NSString *)title keyEquivalent:(NSString *)keyEquivalent actionLambda: (function) func; 74 | @end 75 | 76 | @interface LambdaMenuItem () 77 | - (void)menuAction:(NSMenuItem *)item; 78 | @end 79 | 80 | @implementation LambdaMenuItem 81 | 82 | - (id)initWithTitle:(NSString *)title keyEquivalent:(NSString *)keyEquivalent actionLambda:(function) func { 83 | self = [super initWithTitle:title action:@selector(menuAction:) keyEquivalent:keyEquivalent]; 84 | if (self) { 85 | actionLambda = func; 86 | [self setTarget:self]; 87 | } 88 | return self; 89 | } 90 | 91 | 92 | 93 | - (void)menuAction:(NSMenuItem *)item { 94 | if ((item == self) && (actionLambda)) { 95 | actionLambda(); 96 | } 97 | } 98 | @end 99 | 100 | 101 | 102 | 103 | @interface AppDelegate () { 104 | WKWebView *webView; 105 | NSWindow *window; 106 | } 107 | 108 | @end 109 | 110 | @implementation AppDelegate 111 | 112 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { 113 | // Insert code here to initialize your application 114 | [self makeWindow]; 115 | [self makeMenus]; 116 | [self startCppSketch]; 117 | } 118 | 119 | 120 | - (void) startCppSketch { 121 | 122 | dylib.willCloseDylib = []() { 123 | audioMutex.lock(); 124 | audio = nullptr; 125 | audioMutex.unlock(); 126 | }; 127 | dylib.reloaded = [self](LiveAudioWeb *ptr) { 128 | audioMutex.lock(); 129 | audio = ptr; 130 | ptr->jsExternalCall = [self](const std::string &s) { 131 | // NSLog(@"Executing %s", s.c_str()); 132 | [webView evaluateJavaScript:[NSString stringWithUTF8String:s.c_str()] completionHandler:nil]; 133 | }; 134 | audio->setup(); 135 | audioMutex.unlock(); 136 | }; 137 | 138 | dylib.init("MyLiveAudioWeb.h"); 139 | 140 | 141 | startRtAudio(); 142 | 143 | [NSTimer scheduledTimerWithTimeInterval:1.0 144 | target:self 145 | selector:@selector(checkForChanges) 146 | userInfo:nil 147 | repeats:YES]; 148 | 149 | [NSTimer scheduledTimerWithTimeInterval:1.0/60.0 150 | target:self 151 | selector:@selector(update) 152 | userInfo:nil 153 | repeats:YES]; 154 | } 155 | - (void) update { 156 | audioMutex.lock(); 157 | if(audio!=nullptr) { 158 | audio->update(); 159 | } 160 | audioMutex.unlock(); 161 | } 162 | - (void) checkForChanges { 163 | dylib.checkForChanges(); 164 | } 165 | 166 | - (void)applicationWillTerminate:(NSNotification *)aNotification { 167 | // Insert code here to tear down your application 168 | } 169 | 170 | 171 | 172 | 173 | 174 | - (void) makeMenus { 175 | 176 | NSString *appName = [[NSProcessInfo processInfo] processName]; 177 | 178 | NSApp.mainMenu = [[NSMenu alloc] initWithTitle: @"MainMenu"]; 179 | 180 | NSMenuItem *menuItem = [NSApp.mainMenu addItemWithTitle:appName action:nil keyEquivalent:@""]; 181 | NSMenu *appMenu = [[NSMenu alloc] initWithTitle:appName]; 182 | [NSApp.mainMenu setSubmenu:appMenu forItem:menuItem]; 183 | 184 | 185 | 186 | 187 | id reloadItem = [[LambdaMenuItem alloc] initWithTitle: @"Reload" 188 | keyEquivalent:@"r" actionLambda:[self]() { 189 | [self reload]; 190 | }]; 191 | 192 | [appMenu addItem: reloadItem]; 193 | 194 | [appMenu addItem: [NSMenuItem separatorItem]]; 195 | 196 | 197 | id quitItem = [[LambdaMenuItem alloc] initWithTitle: @"Quit" 198 | keyEquivalent:@"q" actionLambda:[]() { 199 | [[NSApplication sharedApplication] terminate:nil]; 200 | }]; 201 | 202 | [appMenu addItem: quitItem]; 203 | } 204 | 205 | - (void) reload { 206 | NSLog(@"Reload %s", __FILE__); 207 | [webView reload]; 208 | } 209 | 210 | - (void) makeWindow { 211 | 212 | 213 | NSRect windowRect = NSMakeRect(0, 0, 800, 600); 214 | 215 | window = [[NSWindow alloc] 216 | initWithContentRect:windowRect 217 | styleMask:NSTitledWindowMask | NSWindowStyleMaskResizable | NSWindowStyleMaskClosable backing:NSBackingStoreBuffered defer:NO]; 218 | window.acceptsMouseMovedEvents = YES; 219 | 220 | id appName = [[NSProcessInfo processInfo] processName]; 221 | 222 | 223 | [window cascadeTopLeftFromPoint:NSMakePoint(20,20)]; 224 | [window setTitle:appName]; 225 | WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init]; 226 | 227 | [config.userContentController addScriptMessageHandler:self name:@"updateHandler"]; 228 | 229 | 230 | 231 | 232 | webView = [[WKWebView alloc] initWithFrame:windowRect configuration:config]; 233 | 234 | 235 | NSURL *baseURL = [[NSBundle mainBundle] resourceURL]; 236 | NSString *path = [[NSFileManager defaultManager] currentDirectoryPath]; 237 | path = [path stringByAppendingString: @"/index.html"]; 238 | NSURL *url = [NSURL fileURLWithPath: path]; 239 | // NSURL *url = [[NSBundle mainBundle] URLForResource:@"index" withExtension:@"html"]; 240 | [webView loadFileURL:url allowingReadAccessToURL:baseURL]; 241 | // [webView loadHTMLString:@"

Hello!

" baseURL:baseURL]; 242 | 243 | // 244 | 245 | [[window contentView] addSubview:webView]; 246 | [window makeKeyAndOrderFront:nil]; 247 | [window makeMainWindow]; 248 | 249 | } 250 | /* 251 | 252 | - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message { 253 | NSDictionary *dict = message.body; 254 | NSString *func = dict[@"function"]; 255 | NSArray *args = dict[@"args"]; 256 | 257 | string funcStr = string([func UTF8String]); 258 | vector params; 259 | for(int i = 0; i < [args count]; i++) { 260 | id s = [args objectAtIndex: i]; 261 | if([s isKindOfClass: [NSString class]]) { 262 | params.push_back([s UTF8String]); 263 | } else { 264 | params.push_back([[s stringValue] UTF8String]); 265 | } 266 | } 267 | audioMutex.lock(); 268 | if(audio!=nullptr) { 269 | audio->jsReceived(funcStr, params); 270 | } 271 | audioMutex.unlock(); 272 | // [webView evaluateJavaScript:[NSString stringWithUTF8String:s.c_str()] completionHandler:nil]; 273 | 274 | } 275 | */ 276 | 277 | - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message { 278 | NSDictionary *dict = message.body; 279 | std::string key = [dict[@"key"] UTF8String]; 280 | std::string value = [dict[@"value"] UTF8String]; 281 | 282 | audioMutex.lock(); 283 | if(audio!=nullptr) { 284 | audio->setParameter(key, value); 285 | } 286 | audioMutex.unlock(); 287 | 288 | // string funcStr = string([func UTF8String]); 289 | // vector params; 290 | // for(int i = 0; i < [args count]; i++) { 291 | // id s = [args objectAtIndex: i]; 292 | // if([s isKindOfClass: [NSString class]]) { 293 | // params.push_back([s UTF8String]); 294 | // } else { 295 | // params.push_back([[s stringValue] UTF8String]); 296 | // } 297 | // } 298 | // audioMutex.lock(); 299 | // if(audio!=nullptr) { 300 | // audio->jsReceived(funcStr, params); 301 | // } 302 | // audioMutex.unlock(); 303 | // [webView evaluateJavaScript:[NSString stringWithUTF8String:s.c_str()] completionHandler:nil]; 304 | 305 | } 306 | @end 307 | -------------------------------------------------------------------------------- /examples/audioweb/Biquad.h: -------------------------------------------------------------------------------- 1 | // 2 | // Was called - CFxRbjFilter.h 3 | // Someone's implementation of Robert Bristow-Johnson's biquad eqns. 4 | // 5 | // Created by Marek Bereza on 02/10/2012. 6 | // (found it on musicdsp.org) 7 | // Modified 04/04/19 by Marek Bereza 8 | 9 | #pragma once 10 | #include 11 | class Biquad { 12 | public: 13 | 14 | 15 | constexpr static int LOWPASS = 0; 16 | constexpr static int HIGHPASS = 1; 17 | constexpr static int BANDPASS_CSG = 2; 18 | constexpr static int BANDPASS_CZPG = 3; 19 | constexpr static int NOTCH = 4; 20 | constexpr static int ALLPASS = 5; 21 | constexpr static int PEAKING = 6; 22 | constexpr static int LOWSHELF = 7; 23 | constexpr static int HIGHSHELF = 8; 24 | 25 | 26 | 27 | 28 | float filter(float in0) { 29 | // filter 30 | float const yn = b0a0*in0 + b1a0*in1 + b2a0*in2 - a1a0*ou1 - a2a0*ou2; 31 | 32 | // push in/out buffers 33 | in2=in1; 34 | in1=in0; 35 | ou2=ou1; 36 | ou1=yn; 37 | 38 | // return output 39 | return yn; 40 | }; 41 | 42 | 43 | void calc(int const type,double const frequency,double const sample_rate,double const q,double const db_gain,bool q_is_bandwidth) { 44 | // temp pi 45 | double const temp_pi=3.1415926535897932384626433832795; 46 | 47 | // temp coef vars 48 | double alpha,a0,a1,a2,b0,b1,b2; 49 | 50 | // peaking, lowshelf and hishelf 51 | if(type>=6) 52 | { 53 | double const A = pow(10.0,(db_gain/40.0)); 54 | double const omega = 2.0*temp_pi*frequency/sample_rate; 55 | double const tsin = sin(omega); 56 | double const tcos = cos(omega); 57 | 58 | if(q_is_bandwidth) 59 | alpha=tsin*sinh(std::log(2.0)/2.0*q*omega/tsin); 60 | else 61 | alpha=tsin/(2.0*q); 62 | 63 | double const beta = sqrt(A)/q; 64 | 65 | 66 | // peaking 67 | if(type==PEAKING) { 68 | b0=float(1.0+alpha*A); 69 | b1=float(-2.0*tcos); 70 | b2=float(1.0-alpha*A); 71 | a0=float(1.0+alpha/A); 72 | a1=float(-2.0*tcos); 73 | a2=float(1.0-alpha/A); 74 | } 75 | 76 | // lowshelf 77 | else if(type==LOWSHELF) 78 | { 79 | b0=float(A*((A+1.0)-(A-1.0)*tcos+beta*tsin)); 80 | b1=float(2.0*A*((A-1.0)-(A+1.0)*tcos)); 81 | b2=float(A*((A+1.0)-(A-1.0)*tcos-beta*tsin)); 82 | a0=float((A+1.0)+(A-1.0)*tcos+beta*tsin); 83 | a1=float(-2.0*((A-1.0)+(A+1.0)*tcos)); 84 | a2=float((A+1.0)+(A-1.0)*tcos-beta*tsin); 85 | } 86 | 87 | // hishelf 88 | else if(type==HIGHSHELF) 89 | { 90 | b0=float(A*((A+1.0)+(A-1.0)*tcos+beta*tsin)); 91 | b1=float(-2.0*A*((A-1.0)+(A+1.0)*tcos)); 92 | b2=float(A*((A+1.0)+(A-1.0)*tcos-beta*tsin)); 93 | a0=float((A+1.0)-(A-1.0)*tcos+beta*tsin); 94 | a1=float(2.0*((A-1.0)-(A+1.0)*tcos)); 95 | a2=float((A+1.0)-(A-1.0)*tcos-beta*tsin); 96 | } else { 97 | // stop compiler warning 98 | a0 = 0; 99 | b1 = 0; 100 | b2 = 0; 101 | a1 = 0; 102 | a2 = 0; 103 | } 104 | } 105 | else 106 | { 107 | // other filters 108 | double const omega = 2.0*temp_pi*frequency/sample_rate; 109 | double const tsin = sin(omega); 110 | double const tcos = cos(omega); 111 | 112 | if(q_is_bandwidth) 113 | alpha=tsin*sinh(std::log(2.0)/2.0*q*omega/tsin); 114 | else 115 | alpha=tsin/(2.0*q); 116 | 117 | 118 | // lowpass 119 | if(type==LOWPASS) 120 | { 121 | b0=(1.0-tcos)/2.0; 122 | b1=1.0-tcos; 123 | b2=(1.0-tcos)/2.0; 124 | a0=1.0+alpha; 125 | a1=-2.0*tcos; 126 | a2=1.0-alpha; 127 | } 128 | 129 | // hipass 130 | else if(type==HIGHPASS) 131 | { 132 | b0=(1.0+tcos)/2.0; 133 | b1=-(1.0+tcos); 134 | b2=(1.0+tcos)/2.0; 135 | a0=1.0+ alpha; 136 | a1=-2.0*tcos; 137 | a2=1.0-alpha; 138 | } 139 | 140 | // bandpass csg 141 | else if(type==BANDPASS_CSG) 142 | { 143 | b0=tsin/2.0; 144 | b1=0.0; 145 | b2=-tsin/2; 146 | a0=1.0+alpha; 147 | a1=-2.0*tcos; 148 | a2=1.0-alpha; 149 | } 150 | 151 | // bandpass czpg 152 | else if(type==BANDPASS_CZPG) 153 | { 154 | b0=alpha; 155 | b1=0.0; 156 | b2=-alpha; 157 | a0=1.0+alpha; 158 | a1=-2.0*tcos; 159 | a2=1.0-alpha; 160 | } 161 | 162 | // notch 163 | else if(type==NOTCH) 164 | { 165 | b0=1.0; 166 | b1=-2.0*tcos; 167 | b2=1.0; 168 | a0=1.0+alpha; 169 | a1=-2.0*tcos; 170 | a2=1.0-alpha; 171 | } 172 | 173 | // allpass 174 | else if(type==ALLPASS) 175 | { 176 | b0=1.0-alpha; 177 | b1=-2.0*tcos; 178 | b2=1.0+alpha; 179 | a0=1.0+alpha; 180 | a1=-2.0*tcos; 181 | a2=1.0-alpha; 182 | } else { 183 | // stop compiler warning 184 | a0 = 0; 185 | b1 = 0; 186 | b2 = 0; 187 | a1 = 0; 188 | a2 = 0; 189 | } 190 | } 191 | 192 | // set filter coeffs 193 | b0a0=float(b0/a0); 194 | b1a0=float(b1/a0); 195 | b2a0=float(b2/a0); 196 | a1a0=float(a1/a0); 197 | a2a0=float(a2/a0); 198 | }; 199 | 200 | void copyCoeffsFrom(const Biquad &b) { 201 | b0a0 = b.b0a0; 202 | b1a0 = b.b1a0; 203 | b2a0 = b.b2a0; 204 | a1a0 = b.a1a0; 205 | a2a0 = b.a2a0; 206 | } 207 | private: 208 | 209 | // filter coeffs 210 | float b0a0 = 0,b1a0 = 0,b2a0 = 0,a1a0 = 0,a2a0 = 0; 211 | 212 | // in/out history 213 | float ou1 = 0,ou2 = 0,in1 = 0,in2 = 0; 214 | }; 215 | -------------------------------------------------------------------------------- /examples/audioweb/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | cmake_minimum_required(VERSION 3.18) 4 | 5 | set(APP_NAME audioweb) 6 | project(${APP_NAME} VERSION 1.0.0) 7 | 8 | set(CMAKE_CXX_STANDARD 20) 9 | set(CMAKE_CXX_STANDARD_REQUIRED True) 10 | 11 | # Add main.cpp (or other source files as needed) 12 | add_executable(${APP_NAME} 13 | main.mm 14 | AppDelegate.mm 15 | ) 16 | 17 | 18 | # Add the src subdirectory 19 | add_subdirectory(../../src cppsketch) 20 | add_subdirectory(../../ext/rtaudio rtaudio) 21 | # Add the include directory 22 | target_include_directories(${APP_NAME} PRIVATE ../../ext/rtaudio) 23 | # Link the executable with the src_lib 24 | target_link_libraries(${APP_NAME} PRIVATE cppsketch_lib rtaudio) 25 | 26 | # Link the Cocoa and WebKit frameworks on macOS 27 | if(APPLE) 28 | find_library(COCOA_LIBRARY Cocoa) 29 | find_library(WEBKIT_LIBRARY WebKit) 30 | target_link_libraries(audioweb PRIVATE ${COCOA_LIBRARY} ${WEBKIT_LIBRARY}) 31 | endif() 32 | 33 | 34 | # OS-specific options 35 | if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") 36 | # Add -ldl for Linux 37 | target_link_libraries(${APP_NAME} ${CMAKE_DL_LIBS}) 38 | endif() 39 | 40 | set_target_properties(${APP_NAME} PROPERTIES 41 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" 42 | ) 43 | -------------------------------------------------------------------------------- /examples/audioweb/LiveAudioWeb.h: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | 6 | #ifdef __EMSCRIPTEN__ 7 | #include "emscripten.h" 8 | #endif 9 | 10 | class LiveAudioWeb { 11 | public: 12 | 13 | 14 | 15 | // call this to execute js in your page 16 | void executeJS(const std::string &js) { 17 | // if emscripten 18 | #ifdef __EMSCRIPTEN__ 19 | emscripten_run_script(js.c_str()); 20 | #else 21 | // if native 22 | jsExternalCall(js); 23 | #endif 24 | } 25 | 26 | // called at the beginning of the newly reloaded sketch 27 | virtual void setup() {} 28 | 29 | // calls on main thread, so this is where you do your comms 30 | virtual void update() {} 31 | 32 | 33 | virtual void audioOut(float *samples, int length, int numChans) {} 34 | virtual void setParameter(const std::string &key, const std::string &value) {} 35 | // virtual void jsReceived(const std::string &s, const std::vector ¶ms) {} 36 | 37 | // implementation detail, do not change 38 | std::function jsExternalCall; 39 | }; 40 | 41 | -------------------------------------------------------------------------------- /examples/audioweb/MyLiveAudioWeb.h: -------------------------------------------------------------------------------- 1 | #include "LiveAudioWeb.h" 2 | #include "Biquad.h" 3 | #include 4 | 5 | using namespace std; 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | class DubDelayEffectKernel { 17 | public: 18 | vector buffer; 19 | float value = 0; 20 | float actualDelayTime; 21 | int writeHead; 22 | float readHead; 23 | int delay; 24 | float feedback; 25 | float mix; 26 | float lerpAmt; 27 | float lerpAmtM; 28 | static constexpr int MAXDELAY = 44100; 29 | DubDelayEffectKernel() { 30 | actualDelayTime = 0; 31 | writeHead = 0; 32 | readHead = 0; 33 | delay = 11025; 34 | feedback = 0.9; 35 | mix = 0.5; 36 | buffer.resize(MAXDELAY); 37 | fill(buffer.begin(), buffer.end(), 0); 38 | lerpAmt = 0.99975; 39 | lerpAmtM = 1 - lerpAmt; 40 | filt.calc(Biquad::BANDPASS_CSG, 700, 48000, 0.86, 0, false); 41 | } 42 | 43 | void clear() { 44 | fill(buffer.begin(), buffer.end(), 0); 45 | } 46 | 47 | float readFrac(vector &buff, float index) { 48 | int i = index; 49 | int j = index + 1; 50 | if(j >= buff.size()) j -= buff.size(); 51 | float frac = index - i; 52 | 53 | return buff[i] * (1 - frac) + buff[j] * frac; 54 | } 55 | 56 | Biquad filt; 57 | float filter(float in) { 58 | return tanh(filt.filter(in)); 59 | } 60 | 61 | float process(float inp) { 62 | delay = 1000.f + value*21050; 63 | writeHead++; 64 | writeHead %= MAXDELAY; 65 | 66 | actualDelayTime = actualDelayTime * lerpAmt + delay * lerpAmtM; 67 | 68 | readHead = writeHead - actualDelayTime; 69 | if(readHead<0) readHead += MAXDELAY; 70 | 71 | float out = readFrac(buffer, readHead); 72 | 73 | buffer[writeHead] = filter(out * feedback + inp); 74 | 75 | return inp + (out - inp) * mix; 76 | } 77 | }; 78 | 79 | 80 | 81 | 82 | class DubDelayEffect { 83 | public: 84 | DubDelayEffectKernel l, r; 85 | float wetMixValue = 0.5f; 86 | 87 | bool wasEnabled = false; 88 | float value = 0.f; 89 | 90 | void setDelayTime(float dt) { 91 | l.value = dt; 92 | r.value = 0.03 + dt * 0.97; 93 | 94 | } 95 | void setFeedback(float fb) { 96 | l.feedback = fb; 97 | r.feedback = fb; 98 | } 99 | 100 | void setLevel(float level) { 101 | l.mix = level; 102 | r.mix = level; 103 | } 104 | void process(float *b, int length) { 105 | 106 | 107 | if(l.mix<=0.f) return; 108 | 109 | 110 | for(int i = 0; i < length; i++) { 111 | 112 | 113 | b[i*2] = l.process(b[i*2]); 114 | b[i*2+1] = r.process(b[i*2+1]); 115 | } 116 | } 117 | }; 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | class Voice { 140 | public: 141 | 142 | 143 | 144 | int pos = 1000000; 145 | double ph = 0; 146 | 147 | 148 | 149 | 150 | // these are the coefficients - need to get the scale mapping right 151 | float baseFreq = 50; 152 | 153 | // amplitude 154 | float ampCurve = 1.2; 155 | float attackAmt = 40; 156 | float releaseAmt = 10000; 157 | 158 | // fm 159 | float ratio = 2.0f; 160 | float fmAmt = 2.0f; 161 | float fmFeedback = 0.1f; 162 | float fmRelease = 5000; 163 | float fmCurve = 1.2; 164 | 165 | 166 | // pitch 167 | float pitchAmt = 200; 168 | float pitchRelease = 10000; 169 | float pitchCurve = 10; 170 | 171 | 172 | 173 | 174 | float lastOp1 = 0.f; 175 | 176 | 177 | void trigger() { 178 | pos = 0; 179 | ph = 0; 180 | } 181 | 182 | double phase = 0.0; 183 | float level = 0.5; 184 | void setLevel(float l) { 185 | level = l; 186 | } 187 | void setPitch(float p) { 188 | baseFreq = 50 + 1000*p; 189 | } 190 | 191 | void setAttack(float att) { 192 | attackAmt = 10 + att * 100; 193 | } 194 | void setRelease(float rel) { 195 | releaseAmt = 1000 + 20000 * rel; 196 | } 197 | void setRatio(float rat) { 198 | ratio = 0.5 + 12 * rat; 199 | } 200 | void setFMAmt(float fmAmt) { 201 | this->fmAmt = fmAmt * 10; 202 | } 203 | 204 | void setFMFeedback(float fmFeedback) { 205 | this->fmFeedback = fmFeedback; 206 | } 207 | 208 | void setFMRelease(float fmRelease) { 209 | this->fmRelease = 100 + fmRelease * 10000; 210 | } 211 | void setPitchModAmt(float pitchModAmt) { 212 | this->pitchAmt = pitchModAmt * 1000; 213 | } 214 | void setPitchRelease(float pitchRelease) { 215 | this->pitchRelease = 100 + pitchRelease * 20000; 216 | } 217 | 218 | float pan = 0.5; 219 | float reverbSend = 0.f; 220 | float delaySend = 0.f; 221 | 222 | void setPan(float pan) { 223 | this->pan = pan; 224 | } 225 | 226 | void setReverbSend(float reverbSend) { 227 | this->reverbSend = reverbSend; 228 | } 229 | 230 | void setDelaySend(float delaySend) { 231 | this->delaySend = delaySend; 232 | } 233 | 234 | 235 | void getSample(float &L, float &R, float &reverb, float &delay) { 236 | 237 | 238 | 239 | float env = 0.f; 240 | 241 | if(pos0.5f) { 274 | float lVolTarget = 1.f - 2.f * (pan-0.5); 275 | L += out * lVolTarget; 276 | R += out; 277 | } else { 278 | float rVolTarget = 2.f * pan; 279 | L += out; 280 | R += out * rVolTarget; 281 | } 282 | reverb += out * reverbSend; 283 | delay += out * delaySend; 284 | } 285 | 286 | 287 | vector pattern; 288 | Voice() : pattern(16, 0) { 289 | 290 | } 291 | }; 292 | 293 | 294 | class MyLiveAudioWeb : public LiveAudioWeb { 295 | public: 296 | vector voices; 297 | DubDelayEffect del; 298 | 299 | MyLiveAudioWeb() : voices(6) { 300 | 301 | } 302 | 303 | struct SinOsc { 304 | double phase = 0; 305 | double freq = 440; 306 | 307 | SinOsc() { 308 | 309 | } 310 | float getSample() { 311 | phase += freq * M_PI * 2.0 / 48000.f; 312 | return sin(phase); 313 | } 314 | }; 315 | 316 | SinOsc osc; 317 | SinOsc op1; 318 | 319 | int pos = 0; 320 | int beat = 15; 321 | int sendBeat = -1; 322 | void tick() { 323 | sendBeat = beat; 324 | for(auto &v : voices) { 325 | if(v.pattern[beat]) { 326 | v.trigger(); 327 | } 328 | } 329 | } 330 | 331 | int period = 5000; 332 | void getSample(float &L, float &R, float &reverb, float &delay) { 333 | 334 | 335 | 336 | if(pos>period) { 337 | tick(); 338 | pos = 0; 339 | osc.phase = 0; 340 | op1.phase = 0; 341 | beat = (beat+1) % 16; 342 | } 343 | 344 | for(auto &v : voices) { 345 | v.getSample(L, R, reverb, delay); 346 | } 347 | 348 | 349 | pos++; 350 | 351 | } 352 | 353 | int iii = 0; 354 | 355 | void update() override { 356 | if(sendBeat!=-1) { 357 | executeJS("setBeat("+to_string(sendBeat)+")"); 358 | sendBeat = -1; 359 | } 360 | } 361 | 362 | 363 | 364 | vector split(const string& s, char c) { 365 | vector v; 366 | string::size_type i = 0; 367 | string::size_type j = s.find(c); 368 | 369 | while (j != string::npos) { 370 | v.push_back(s.substr(i, j-i)); 371 | i = ++j; 372 | j = s.find(c, j); 373 | 374 | if (j == string::npos) 375 | v.push_back(s.substr(i, s.length())); 376 | } 377 | return v; 378 | } 379 | 380 | 381 | 382 | void setParameter(const std::string &key, const std::string &value) override { 383 | // printf("%s = %s\n", key.c_str(), value.c_str()); 384 | auto parts = split(key, '_'); 385 | if(parts[0]=="param") { 386 | int voiceNo = stoi(parts[1]); 387 | string param = parts[2]; 388 | 389 | 390 | 391 | float val = stof(value); 392 | if(param=="pitch") { 393 | voices[voiceNo].setPitch(val); 394 | } else if(param=="attack") { 395 | voices[voiceNo].setAttack(val); 396 | } else if(param=="release") { 397 | voices[voiceNo].setRelease(val); 398 | } else if(param=="ratio") { 399 | voices[voiceNo].setRatio(val); 400 | } else if(param=="fmAmt") { 401 | voices[voiceNo].setFMAmt(val); 402 | } else if(param=="fmFeedback") { 403 | voices[voiceNo].setFMFeedback(val); 404 | } else if(param=="fmRelease") { 405 | voices[voiceNo].setFMRelease(val); 406 | } else if(param=="pitchModAmt") { 407 | voices[voiceNo].setPitchModAmt(val); 408 | } else if(param=="pitchRelease") { 409 | voices[voiceNo].setPitchRelease(val); 410 | } else if(param=="level") { 411 | voices[voiceNo].setLevel(val); 412 | } else if(param=="pan") { 413 | voices[voiceNo].setPan(val); 414 | } else if(param=="reverbSend") { 415 | voices[voiceNo].setReverbSend(val); 416 | } else if(param=="delaySend") { 417 | voices[voiceNo].setDelaySend(val); 418 | } 419 | 420 | 421 | } else if(parts[0]=="glob") { 422 | if(parts[1]=="delayTime") { 423 | del.setDelayTime(stof(value)); 424 | } else if(parts[1]=="delayFeedback") { 425 | del.setFeedback(stof(value)); 426 | } else if(parts[1]=="delayLevel") { 427 | del.setLevel(stof(value)); 428 | } else if(parts[1]=="bpm") { 429 | float bpm = stof(value); 430 | float bps = bpm / 60.f; 431 | float sampsPerBeat = 44100.f / bps; 432 | period = sampsPerBeat/4; 433 | printf("%d\n", period); 434 | } 435 | } else if(parts[0]=="seq") { 436 | int voiceNo = stoi(parts[1]); 437 | int stepNo = stoi(parts[2]); 438 | bool on = value=="true"; 439 | voices[voiceNo].pattern[stepNo] = on; 440 | } 441 | } 442 | 443 | vector delaySignal; 444 | void audioOut(float *samples, int length, int numChans) override { 445 | if(delaySignal.size()start ▶ 122 | } 123 | 124 | 125 | -------------------------------------------------------------------------------- /examples/audioweb/emscripten/em.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "MyLiveAudioWeb.h" 3 | 4 | MyLiveAudioWeb audioweb; 5 | 6 | extern "C" { 7 | 8 | void getSamples(float *samples, int length, int numChans) { 9 | audioweb.audioOut(samples, length, numChans); 10 | } 11 | 12 | void update() { 13 | audioweb.update(); 14 | } 15 | 16 | void setParameter(char *key, char *value) { 17 | audioweb.setParameter(key, value); 18 | } 19 | 20 | }; -------------------------------------------------------------------------------- /examples/audioweb/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elf-audio/cppsketch/9d0e5733e98403d33e00d6d4a3808b1bffc25df1/examples/audioweb/favicon.png -------------------------------------------------------------------------------- /examples/audioweb/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 154 | 350 | 351 | 352 | 353 | 354 | start ▶ 355 | 356 | SEND 357 |
    358 |
  • Voice 1
  • 359 |
  • Voice 2
  • 360 |
  • Voice 3
  • 361 |
  • Voice 4
  • 362 |
  • Voice 5
  • 363 |
  • Voice 6
  • 364 |
365 |
366 | 367 | 368 |
369 |

Global

370 | 371 | 372 |
0.0
373 | 374 | 375 | 376 |
0.0
377 | 378 | 379 | 380 |
0.0
381 | 382 | 383 |
0.0
384 |
385 | 386 |
387 |

Pitch

388 | 389 | 390 | 391 |
0.0
392 | 393 | 394 | 395 |
0.0
396 | 397 | 398 | 399 |
0.0
400 | 401 |
402 | 403 | 404 |
405 |

FM

406 | 407 | 408 | 409 |
0.0
410 | 411 | 412 | 413 |
0.0
414 | 415 | 416 | 417 |
0.0
418 | 419 | 420 | 421 |
0.0
422 |
423 | 424 | 425 |
426 |

Amplitude

427 | 428 | 429 | 430 |
0.0
431 | 432 | 433 | 434 |
0.0
435 | 436 | 437 | 438 |
0.0
439 | 440 | 441 | 442 |
0.0
443 | 444 | 445 | 446 | 447 | 448 |
0.0
449 | 450 | 451 |
0.0
452 |
453 |
454 |
455 |
456 | 457 | 458 | 459 | 460 |
461 |
462 | 463 | 464 | 465 | 466 |
467 |
468 | 469 | 470 | 471 | 472 |
473 |
474 | 475 | 476 | 477 | 478 |
479 |
480 | 483 | 498 | 499 |
500 | 		
501 | 		What I would like:
502 | 		- pan control
503 | 		- buffer at a time processing with delayed triggers
504 | 		- actual FM
505 | 		
506 | 	
507 | 508 | -------------------------------------------------------------------------------- /examples/audioweb/main.mm: -------------------------------------------------------------------------------- 1 | 2 | 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char * argv[]) { 6 | 7 | [NSApplication sharedApplication]; 8 | [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; 9 | 10 | 11 | [NSApp activateIgnoringOtherApps:YES]; 12 | id appDelegate = [[AppDelegate alloc] init]; 13 | [NSApp setDelegate:appDelegate]; 14 | [NSApp run]; 15 | return 0; 16 | } 17 | -------------------------------------------------------------------------------- /examples/openFrameworks/README.md: -------------------------------------------------------------------------------- 1 | # cppsketch with openFrameworks 2 | 3 | This only works on the mac, only tested with Xcode 11. 4 | 5 | ## HOW TO USE cppsketch with openFrameworks 6 | 1. Drop all the files in src into an openFrameworks empty project 7 | 2. Change your main.cpp to `#include "ofxCppSketch.h"` 8 | 3. change the line 9 | `ofRunApp(new ofApp());` 10 | to 11 | `ofRunApp(new ofxCppSketch("ofApp", __FILE__));` 12 | 4. Go into project build settings, find "Dead code stripping" - change it to NO 13 | 14 | Now, every time you save ofApp.h or ofApp.cpp whilst your app is running, it will recompile in the background - errors are displayed in the xcode console if compilation fails. -------------------------------------------------------------------------------- /examples/openFrameworks/main.cpp: -------------------------------------------------------------------------------- 1 | #include "ofMain.h" 2 | #include "ofApp.h" 3 | #include "ofxCppSketch.h" 4 | 5 | //======================================================================== 6 | int main( ){ 7 | 8 | ofSetupOpenGL(1024,768, OF_WINDOW); // <-------- setup the GL context 9 | 10 | // this kicks off the running of my app 11 | // can be OF_WINDOW or OF_FULLSCREEN 12 | // pass in width and height too: 13 | ofRunApp(new ofxCppSketch("ofApp", __FILE__)); 14 | } 15 | -------------------------------------------------------------------------------- /examples/openFrameworks/ofApp.cpp: -------------------------------------------------------------------------------- 1 | #include "ofApp.h" 2 | 3 | 4 | //-------------------------------------------------------------- 5 | void ofApp::setup(){ 6 | ofBackground(0); 7 | ofSetCircleResolution(66); 8 | ofSetWindowShape(400, 400); 9 | particles.resize(10000); 10 | ofEnableBlendMode(OF_BLENDMODE_ADD); 11 | } 12 | 13 | //-------------------------------------------------------------- 14 | void ofApp::update(){ 15 | for(auto &p : particles) { 16 | p.update(); 17 | } 18 | } 19 | 20 | //-------------------------------------------------------------- 21 | void ofApp::draw(){ 22 | ofVboMesh mesh; 23 | 24 | for(auto &p : particles) { 25 | mesh.addVertex(p.pos); 26 | //p.draw(); 27 | } 28 | mesh.draw(OF_MESH_POINTS); 29 | } 30 | 31 | //-------------------------------------------------------------- 32 | void ofApp::keyPressed(int key){ 33 | } 34 | 35 | //-------------------------------------------------------------- 36 | void ofApp::keyReleased(int key){ 37 | } 38 | 39 | //-------------------------------------------------------------- 40 | void ofApp::mouseMoved(int x, int y){ 41 | } 42 | 43 | //-------------------------------------------------------------- 44 | void ofApp::mouseDragged(int x, int y, int button){ 45 | } 46 | 47 | //-------------------------------------------------------------- 48 | void ofApp::mousePressed(int x, int y, int button){ 49 | } 50 | 51 | //-------------------------------------------------------------- 52 | void ofApp::mouseReleased(int x, int y, int button){ 53 | } 54 | 55 | //-------------------------------------------------------------- 56 | void ofApp::mouseEntered(int x, int y){ 57 | } 58 | 59 | //-------------------------------------------------------------- 60 | void ofApp::mouseExited(int x, int y){ 61 | } 62 | 63 | //-------------------------------------------------------------- 64 | void ofApp::windowResized(int w, int h){ 65 | } 66 | 67 | //-------------------------------------------------------------- 68 | void ofApp::gotMessage(ofMessage msg){ 69 | } 70 | 71 | //-------------------------------------------------------------- 72 | void ofApp::dragEvent(ofDragInfo dragInfo){ 73 | } 74 | -------------------------------------------------------------------------------- /examples/openFrameworks/ofApp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ofMain.h" 4 | 5 | class Particle { 6 | public: 7 | glm::vec3 pos; 8 | float speed = 1; 9 | Particle() { 10 | pos = {ofRandom(0, ofGetWidth()), ofRandom(0, ofGetHeight()), 0}; 11 | speed = ofRandom(0.5, 2.f); 12 | } 13 | void update() { 14 | pos += (glm::vec3(ofNoise(pos.x*0.01*speed + ofGetElapsedTimef()*0.4, pos.y*0.001), ofNoise(pos.x*0.0015, pos.y*0.0024), 0) - glm::vec3(0.5, 0.5, 0.f)) * 4.f * speed; 15 | 16 | if(pos.x<0) pos.x += ofGetWidth(); 17 | else if(pos.x>ofGetWidth()) pos.x -= ofGetWidth(); 18 | 19 | if(pos.y<0) pos.y += ofGetHeight(); 20 | else if(pos.y>ofGetHeight()) pos.y -= ofGetHeight(); 21 | 22 | } 23 | }; 24 | 25 | class ofApp : public ofBaseApp{ 26 | public: 27 | void setup(); 28 | void update(); 29 | void draw(); 30 | 31 | void keyPressed(int key); 32 | void keyReleased(int key); 33 | void mouseMoved(int x, int y); 34 | void mouseDragged(int x, int y, int button); 35 | void mousePressed(int x, int y, int button); 36 | void mouseReleased(int x, int y, int button); 37 | void mouseEntered(int x, int y); 38 | void mouseExited(int x, int y); 39 | void windowResized(int w, int h); 40 | void dragEvent(ofDragInfo dragInfo); 41 | void gotMessage(ofMessage msg); 42 | vector particles; 43 | }; 44 | -------------------------------------------------------------------------------- /examples/simple/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | cmake_minimum_required(VERSION 3.18) 4 | 5 | set(APP_NAME simple) 6 | project(${APP_NAME} VERSION 1.0.0) 7 | 8 | set(CMAKE_CXX_STANDARD 20) 9 | set(CMAKE_CXX_STANDARD_REQUIRED True) 10 | 11 | # Add main.cpp (or other source files as needed) 12 | add_executable(${APP_NAME} 13 | main.cpp 14 | ) 15 | 16 | # Add the src subdirectory 17 | add_subdirectory(../../src cppsketch) 18 | 19 | # Link the executable with the src_lib 20 | target_link_libraries(${APP_NAME} PRIVATE cppsketch_lib) 21 | 22 | # OS-specific options 23 | if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") 24 | # Add -ldl for Linux 25 | target_link_libraries(${APP_NAME} ${CMAKE_DL_LIBS}) 26 | endif() 27 | 28 | set_target_properties(${APP_NAME} PROPERTIES 29 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" 30 | ) 31 | -------------------------------------------------------------------------------- /examples/simple/LiveApp.h: -------------------------------------------------------------------------------- 1 | 2 | class LiveApp { 3 | public: 4 | virtual void setup() {} 5 | virtual void update() {} 6 | }; 7 | -------------------------------------------------------------------------------- /examples/simple/MyLiveApp.h: -------------------------------------------------------------------------------- 1 | 2 | #include "LiveApp.h" 3 | 4 | #include 5 | #include 6 | 7 | class MyLiveApp : public LiveApp { 8 | public: 9 | void setup() override { printf("setup\n"); } 10 | 11 | int i = 0; 12 | 13 | void update() override { 14 | i += 1; 15 | printf("update %d\n", i); 16 | 17 | // sleep for 0.1 seconds 18 | usleep(1000 * 1000); 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /examples/simple/main.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include "ReloadableClass.h" 4 | #include "LiveApp.h" 5 | 6 | LiveApp *app = nullptr; 7 | 8 | int main(int argc, char * argv[]) { 9 | 10 | ReloadableClass dylib; 11 | 12 | dylib.reloaded = [](LiveApp *ptr) { 13 | app = ptr; 14 | app->setup(); 15 | }; 16 | 17 | dylib.init("MyLiveApp.h"); 18 | 19 | while(1) { 20 | dylib.checkForChanges(); 21 | if(app!=nullptr) { 22 | app->update(); 23 | } 24 | } 25 | 26 | return 0; 27 | } -------------------------------------------------------------------------------- /examples/tetris/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | cmake_minimum_required(VERSION 3.18) 4 | 5 | set(APP_NAME tetris) 6 | project(${APP_NAME} VERSION 1.0.0) 7 | 8 | set(CMAKE_CXX_STANDARD 20) 9 | set(CMAKE_CXX_STANDARD_REQUIRED True) 10 | 11 | # Add main.cpp (or other source files as needed) 12 | add_executable(${APP_NAME} 13 | main.cpp 14 | ) 15 | 16 | # Add the src subdirectory 17 | add_subdirectory(../../src cppsketch) 18 | add_subdirectory(../../ext/rtaudio rtaudio) 19 | # Add the include directory 20 | target_include_directories(${APP_NAME} PRIVATE ../../ext/rtaudio) 21 | # Link the executable with the src_lib 22 | target_link_libraries(${APP_NAME} cppsketch_lib rtaudio) 23 | 24 | # OS-specific options 25 | if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") 26 | # Add -ldl for Linux 27 | target_link_libraries(${APP_NAME} ${CMAKE_DL_LIBS}) 28 | endif() 29 | 30 | set_target_properties(${APP_NAME} PROPERTIES 31 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" 32 | ) 33 | -------------------------------------------------------------------------------- /examples/tetris/LiveAudio.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | class LiveAudio { 4 | public: 5 | virtual void setup() {} 6 | virtual void audioOut(float *samples, int length, int numChans) {} 7 | }; 8 | 9 | -------------------------------------------------------------------------------- /examples/tetris/MyLiveAudio.h: -------------------------------------------------------------------------------- 1 | #include "LiveAudio.h" 2 | 3 | #include 4 | #include 5 | using namespace std; 6 | 7 | class MyLiveAudio : public LiveAudio { 8 | public: 9 | 10 | float random() { 11 | return (rand() % 10000) / 5000.f - 1.f; 12 | } 13 | int counter = 0; 14 | 15 | float kickEnvelope = 0.f; 16 | float snareEnvelope = 0.f; 17 | float hatEnvelope = 0.f; 18 | 19 | int beat = 0; 20 | 21 | vector kick = { 22 | 1, 0, 0, 1, 23 | 0, 0, 0.8, 0 24 | }; 25 | 26 | vector snare = { 27 | 0, 0, 0, 0, 28 | 1, 0, 0, 0 29 | }; 30 | vector hat = { 31 | 0.5, 0.25, 1, 0.25, 32 | 0.1, 0.25, 1, 0.25 33 | }; 34 | 35 | class Filter { 36 | public: 37 | float c = 0.5; 38 | float out = 0.f; 39 | float loPass(float f) { 40 | out = out * c + f * (1.f - c); 41 | return out; 42 | } 43 | float hiPass(float f) { 44 | return f - loPass(f); 45 | } 46 | }; 47 | 48 | class Voice { 49 | public: 50 | float mtof(float f) { 51 | return (8.17579891564 * exp(.0577622650 * f)); 52 | } 53 | int pos = 0; 54 | double frequency = 0.f; 55 | float envelope = 0; 56 | double phase = 0.f; 57 | int duration = 0; 58 | 59 | vector melody = { 60 | 48, 2, 43, 1, 44, 1, 46, 2, 44, 1, 43, 1, 41, 2, 41, 1, 44, 1, 48, 2, 46, 1, 61 | 44, 1, 43, 3, 44, 1, 46, 2, 48, 2, 44, 2, 41, 2, 41, 4, 0, 0, 46, 2, 49, 1, 62 | 53, 2, 51, 1, 49, 1, 48, 3, 44, 1, 48, 2, 46, 1, 44, 1, 43, 3, 44, 0, 46, 2, 63 | 48, 2, 44, 2, 41, 2, 41, 4}; 64 | 65 | void tick() { 66 | duration--; 67 | if(duration<=0) { 68 | 69 | if(melody[pos*2]!=0) { 70 | frequency = mtof(20 + melody[pos*2]); 71 | duration = melody[pos*2+1]; 72 | envelope = 1; 73 | phase = 0; 74 | } 75 | pos++; 76 | pos %= melody.size()/2; 77 | } 78 | } 79 | 80 | float getSample() { 81 | envelope *= 0.9997; 82 | float out = (sin(phase) + sin(phase * 3)*0.5 + sin(phase * 5)*0.25 + sin(phase * 7)*0.125) * 0.2 * envelope; 83 | phase += frequency * M_PI * 2.f / 44100.f; 84 | return out; 85 | } 86 | }; 87 | 88 | 89 | Filter snareFilter; 90 | Filter hatFilter; 91 | Voice voice; 92 | 93 | float getSample() { 94 | 95 | if(counter==0) { 96 | 97 | if(kick[beat]>0) { 98 | kickEnvelope = kick[beat]; 99 | } 100 | 101 | if(snare[beat]>0) { 102 | snareEnvelope = snare[beat]; 103 | } 104 | if(hat[beat%hat.size()]>0) { 105 | hatEnvelope = hat[beat]; 106 | } 107 | 108 | voice.tick(); 109 | beat++; 110 | beat %= kick.size(); 111 | } 112 | 113 | kickEnvelope *= 0.999f; 114 | snareEnvelope *= 0.9993f; 115 | hatEnvelope *= 0.99; 116 | return 117 | kickEnvelope * sin(counter * 0.01) + 118 | snareFilter.loPass(snareEnvelope * random()) + 119 | hatFilter.hiPass(hatEnvelope * random()) + 120 | voice.getSample() 121 | ; 122 | } 123 | 124 | 125 | 126 | 127 | 128 | void audioOut(float *samples, int length, int numChans) override { 129 | snareFilter.c = 0.5; 130 | for(int i = 0; i < length; i++) { 131 | counter++; 132 | if(counter>8000) { 133 | counter = 0; 134 | } 135 | samples[i*2] = samples[i*2+1] = getSample(); 136 | } 137 | } 138 | }; 139 | -------------------------------------------------------------------------------- /examples/tetris/main.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include 4 | #include "ReloadableClass.h" 5 | #include "LiveAudio.h" 6 | #include "RtAudio.h" 7 | #include 8 | 9 | LiveAudio *audio = nullptr; 10 | 11 | std::mutex audioMutex; 12 | 13 | // Two-channel sawtooth wave generator. 14 | int audioOutCallback(void *outputBuffer, 15 | void *inputBuffer, 16 | unsigned int nBufferFrames, 17 | double streamTime, 18 | RtAudioStreamStatus status, 19 | void *userData) { 20 | float *output = (float *) outputBuffer; 21 | audioMutex.lock(); 22 | if (audio) { 23 | audio->audioOut(output, nBufferFrames, 2); 24 | } else { 25 | memset(output, 0, nBufferFrames * 2 * sizeof(float)); 26 | } 27 | audioMutex.unlock(); 28 | return 0; 29 | } 30 | 31 | RtAudio rtAudio; 32 | 33 | void startRtAudio() { 34 | RtAudio::StreamParameters parameters; 35 | parameters.deviceId = rtAudio.getDefaultOutputDevice(); 36 | parameters.nChannels = 2; 37 | parameters.firstChannel = 0; 38 | unsigned int bufferFrames = 256; // 256 sample frames 39 | 40 | try { 41 | rtAudio.openStream(¶meters, NULL, RTAUDIO_FLOAT32, 44100, &bufferFrames, &audioOutCallback, nullptr); 42 | rtAudio.startStream(); 43 | } catch (RtAudioError &e) { 44 | e.printMessage(); 45 | exit(0); 46 | } 47 | } 48 | 49 | int main(int argc, char *argv[]) { 50 | ReloadableClass dylib; 51 | 52 | dylib.willCloseDylib = []() { 53 | audioMutex.lock(); 54 | audio = nullptr; 55 | audioMutex.unlock(); 56 | }; 57 | dylib.reloaded = [](LiveAudio *ptr) { 58 | audioMutex.lock(); 59 | audio = ptr; 60 | audio->setup(); 61 | audioMutex.unlock(); 62 | }; 63 | 64 | dylib.init("MyLiveAudio.h"); 65 | 66 | startRtAudio(); 67 | 68 | while (1) { 69 | dylib.checkForChanges(); 70 | 71 | usleep(1000 * 1000); 72 | } 73 | 74 | return 0; 75 | } 76 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.18) 2 | 3 | # Project's name 4 | project(cppsketch) 5 | 6 | # Set C++ standard 7 | set(CMAKE_CXX_STANDARD 20) 8 | set(CMAKE_CXX_STANDARD_REQUIRED True) 9 | 10 | # Add source files 11 | add_library(cppsketch_lib 12 | Dylib.cpp 13 | FileWatcher.cpp 14 | liveCodeUtils.cpp 15 | ) 16 | 17 | # Specify include directories 18 | target_include_directories(cppsketch_lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) -------------------------------------------------------------------------------- /src/Dylib.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include "Dylib.h" 4 | 5 | #include 6 | 7 | using namespace std; 8 | 9 | bool Dylib::open(string path) { 10 | close(); 11 | 12 | dylib = dlopen(path.c_str(), RTLD_LAZY); 13 | if(dylib==nullptr) { 14 | printf("Got dlopen error: %s\n", dlerror()); 15 | } 16 | return dylib != nullptr; 17 | } 18 | 19 | void *Dylib::get(string methodName) { 20 | void *ptrFunc = dlsym(dylib, methodName.c_str()); 21 | if(ptrFunc!=nullptr) { 22 | return ((void *(*)())ptrFunc)(); 23 | } else { 24 | printf("Couldn't find the %s() function\n", methodName.c_str()); 25 | return nullptr; 26 | } 27 | } 28 | 29 | void Dylib::close() { 30 | if(dylib!=nullptr) { 31 | dlclose(dylib); 32 | dylib = nullptr; 33 | } 34 | } 35 | 36 | bool Dylib::isOpen() { 37 | return dylib != nullptr; 38 | } 39 | -------------------------------------------------------------------------------- /src/Dylib.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | class Dylib { 5 | public: 6 | 7 | bool open(std::string path); 8 | void *get(std::string methodName); 9 | void close(); 10 | 11 | bool isOpen(); 12 | private: 13 | 14 | void *dylib = nullptr; 15 | }; 16 | -------------------------------------------------------------------------------- /src/FileWatcher.cpp: -------------------------------------------------------------------------------- 1 | #include "FileWatcher.h" 2 | 3 | 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | void FileWatcher::watch(const string &path) { 10 | // this checks to see if the file has already been added, and only 11 | // adds if it's not already here. 12 | // should really be a set but can't modify when iterating 13 | for(auto &v : watchedFiles) { 14 | 15 | if(v.path==path) { 16 | printf("Already has %s\n", path.c_str()); 17 | return; 18 | } 19 | } 20 | watchedFiles.emplace_back(path); 21 | printf("Added %s\n", path.c_str()); 22 | } 23 | 24 | 25 | 26 | void FileWatcher::tick() { 27 | bool hasBeenTouched = false; 28 | for(auto &file : watchedFiles) { 29 | struct stat fileStat; 30 | if(stat(file.path.c_str(), &fileStat) < 0) { 31 | printf("Couldn't find file %s\n", file.path.c_str()); 32 | return; 33 | } 34 | long currUpdateTime = fileStat.st_mtime; 35 | if(currUpdateTime!=file.prevUpdateTime) { 36 | hasBeenTouched = true; 37 | } 38 | file.prevUpdateTime = currUpdateTime; 39 | } 40 | 41 | if(hasBeenTouched && touched!=nullptr) touched(); 42 | } 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/FileWatcher.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | class FileWatcher { 8 | public: 9 | std::function touched; 10 | 11 | void watch(const std::string &path); 12 | // for now you have to call tick() every frame or so to check whether file has been updated 13 | void tick(); 14 | 15 | private: 16 | struct WatchedFile { 17 | std::string path; 18 | long prevUpdateTime = 0; 19 | WatchedFile(const std::string &path) 20 | : path(path) {} 21 | }; 22 | 23 | std::vector watchedFiles; 24 | }; 25 | -------------------------------------------------------------------------------- /src/ReloadableClass.h: -------------------------------------------------------------------------------- 1 | // 2 | // MZGL 3 | // 4 | // Created by Marek Bereza on 15/01/2018. 5 | // Copyright © 2018 Marek Bereza. All rights reserved. 6 | // 7 | /* 8 | 9 | 10 | for this to work, you need to put this in your Other C++ flags 11 | -DSRCROOT=\"${SRCROOT}\" 12 | 13 | 14 | found some good nuggets here 15 | https://glandium.org/blog/?p=2764 16 | about how to do precompiled headers. 17 | 18 | What this does is precompile headers when the app starts if we're doing a LiveCodeApp. 19 | It shouldn't really be in here, this should be for a general RecompilingDylib 20 | 21 | */ 22 | 23 | #pragma once 24 | #include "FileWatcher.h" 25 | #include "Dylib.h" 26 | #include "liveCodeUtils.h" 27 | #include 28 | #include 29 | #include 30 | #include 31 | namespace fs = std::filesystem; 32 | 33 | template 34 | class ReloadableClass { 35 | public: 36 | Dylib dylib; 37 | 38 | FileWatcher watcher; 39 | std::string path; 40 | 41 | std::function reloaded; 42 | std::function willCloseDylib; 43 | 44 | std::function reloadFailed; 45 | std::vector includePaths; 46 | std::string prefixHeader; 47 | std::string cppFile = ""; 48 | 49 | /** 50 | * Parameters: 51 | * path - path to the header file of the live class - must have the same name as the class itself 52 | * includePaths - optional all the paths to include 53 | * headerToPrecompile - optional - if you pass in a main header file, it will be precompiled for faster loading times. 54 | */ 55 | void init(const std::string &path, 56 | const std::vector &includePaths = {}, 57 | const std::string &headerToPrecompile = "") { 58 | liveCodeUtils::init(); 59 | this->includePaths = includePaths; 60 | this->path = findFile(path); 61 | 62 | prefixHeader = headerToPrecompile; 63 | if (headerToPrecompile != "") { 64 | precompileHeader(headerToPrecompile); 65 | } 66 | 67 | watcher.watch(this->path); 68 | 69 | cppFile = getCppFileForHeader(this->path); 70 | 71 | if (cppFile != "") { 72 | watcher.watch(this->cppFile); 73 | } 74 | 75 | watcher.touched = [this]() { recompile(); }; 76 | } 77 | 78 | void checkForChanges() { watcher.tick(); } 79 | 80 | std::string getCppFileForHeader(std::string p) { 81 | int lastDot = p.rfind('.'); 82 | if (lastDot == -1) return ""; 83 | std::string cpp = p.substr(0, lastDot) + ".cpp"; 84 | printf("cpp file is at %s\n", cpp.c_str()); 85 | if (fs::exists(cpp)) return cpp; 86 | return ""; 87 | } 88 | 89 | void precompileHeader(const std::string &headerToPrecompile) { 90 | std::string cmd = "g++ -std=c++11 -x c++-header -stdlib=libc++ " 91 | + liveCodeUtils::includeListToCFlags(includePaths) + " " + headerToPrecompile; 92 | printf("Precompiling headers: %s\n", cmd.c_str()); 93 | liveCodeUtils::execute(cmd); 94 | } 95 | 96 | private: 97 | std::string lastErrorStr; 98 | void recompile() { 99 | //printf("Need to recompile %s\n", path.c_str()); 100 | float t = liveCodeUtils::getSeconds(); 101 | 102 | std::string dylibPath = cc(); 103 | if (dylibPath != "") { 104 | auto *obj = loadDylib(dylibPath); 105 | if (obj != nullptr) { 106 | printf( 107 | "\xE2\x9C\x85\xE2\x9C\x85\xE2\x9C\x85 Success loading \xE2\x9C\x85\xE2\x9C\x85\xE2\x9C\x85\n"); 108 | printf("Compile took %.0fms\n", (liveCodeUtils::getSeconds() - t) * 1000.f); 109 | if (reloaded) reloaded(obj); 110 | } else { 111 | printf( 112 | "\xE2\x9D\x8C\xE2\x9D\x8C\xE2\x9D\x8C Error: No dice loading dylib \xE2\x9D\x8C\xE2\x9D\x8C\xE2\x9D\x8C\n"); 113 | if (reloadFailed) reloadFailed("Couldn't read dylib\n"); 114 | } 115 | 116 | } else { 117 | if (reloadFailed) reloadFailed(lastErrorStr); 118 | } 119 | } 120 | 121 | std::string getAllIncludes() { 122 | std::string userIncludes = liveCodeUtils::includeListToCFlags(includePaths); 123 | return " -I. " //getAllIncludes(string(SRCROOT)) 124 | + userIncludes 125 | // this is the precomp header + " -include " + string(LIBROOT) + "/mzgl/App.h" 126 | //+ getAllIncludes(string(LIBROOT)) + "/mzgl/ " 127 | //+ getAllIncludes(string(LIBROOT)+"/../opt/dsp/") 128 | ; 129 | } 130 | 131 | std::string cc() { 132 | // call our makefile 133 | std::string objectName = getObjectName(path); 134 | std::string objFile = "/tmp/" + objectName + ".o"; 135 | std::string cppFile = "/tmp/" + objectName + ".cpp"; 136 | std::string dylibPath = "/tmp/" + objectName + ".dylib"; 137 | makeCppFile(cppFile, objectName); 138 | 139 | std::string includes = getAllIncludes(); 140 | 141 | std::string prefixHeaderFlag = ""; 142 | if (prefixHeader != "") { 143 | prefixHeaderFlag = "-include " + prefixHeader + " "; 144 | } 145 | std::string cppFlags = ""; 146 | 147 | #ifdef __APPLE__ 148 | cppFlags += " -stdlib=libc++ "; 149 | #endif 150 | std::string cmd = "g++ -g -std=c++11 " + cppFlags + " " + prefixHeaderFlag 151 | + " -Wno-deprecated-declarations -c " + cppFile + " -o " + objFile + " " + includes; 152 | 153 | int exitCode = 0; 154 | std::string res = liveCodeUtils::execute(cmd, &exitCode); 155 | 156 | //printf("Exit code : %d\n", exitCode); 157 | if (exitCode == 0) { 158 | std::string linkerFlags = ""; 159 | #ifdef __APPLE__ 160 | linkerFlags += " -dynamiclib -undefined dynamic_lookup "; 161 | #else 162 | linkerFlags = " -shared "; 163 | #endif 164 | cmd = "g++ -g " + linkerFlags + " -o " + dylibPath + " " + objFile; 165 | liveCodeUtils::execute(cmd); 166 | return dylibPath; 167 | } else { 168 | lastErrorStr = res; 169 | return ""; 170 | } 171 | } 172 | 173 | void makeCppFile(std::string path, std::string objName) { 174 | std::ofstream outFile(path.c_str()); 175 | 176 | outFile << "#include \"" + objName + ".h\"\n\n"; 177 | outFile << "extern \"C\" {\n\n"; 178 | outFile << "\n\nvoid *getPluginPtr() {return new " + objName + "(); };\n\n"; 179 | if (cppFile != "") { 180 | // include the contents of the cpp file if it exists 181 | std::ifstream inFile(cppFile); 182 | std::string line; 183 | if (inFile.is_open()) { 184 | while (getline(inFile, line)) { 185 | outFile << line << '\n'; 186 | } 187 | inFile.close(); 188 | } else { 189 | printf("Error reading %s\n", cppFile.c_str()); 190 | } 191 | } 192 | outFile << "}\n\n"; 193 | 194 | outFile.close(); 195 | } 196 | 197 | std::string getObjectName(std::string p) { 198 | // split on last '/' 199 | int indexOfLastSlash = p.rfind('/'); 200 | if (indexOfLastSlash != -1) { 201 | p = p.substr(indexOfLastSlash + 1); 202 | } 203 | 204 | // split on last '.' 205 | int indexOfLastDot = p.rfind('.'); 206 | if (indexOfLastDot != -1) { 207 | p = p.substr(0, indexOfLastDot); 208 | } 209 | return p; 210 | } 211 | 212 | T *loadDylib(std::string dylibPath) { 213 | if (dylib.isOpen()) { 214 | if (willCloseDylib) willCloseDylib(); 215 | dylib.close(); 216 | } 217 | 218 | if (!dylib.open(dylibPath)) { 219 | return nullptr; 220 | } 221 | return (T *) dylib.get("getPluginPtr"); 222 | } 223 | 224 | ///////////////////////////////////////////////////////// 225 | fs::path findFile(std::string file) { 226 | fs::path f(file); 227 | if (fs::exists(f)) return f; 228 | f = "src" / f; 229 | if (fs::exists(f)) return f; 230 | f = ".." / f; 231 | if (fs::exists(f)) return f; 232 | //f = string(SRCROOT) + "/" + file; 233 | //if(fileExists(f)) return f; 234 | f = fs::current_path() / file; 235 | if (fs::exists(f)) return f; 236 | printf("Error: can't find source file %s\n", file.c_str()); 237 | return f; 238 | } 239 | }; 240 | -------------------------------------------------------------------------------- /src/liveCodeUtils.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // glue.h 3 | // example-simple 4 | // 5 | // Created by Marek Bereza on 16/10/2019. 6 | // 7 | 8 | #include "liveCodeUtils.h" 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | using namespace std; 17 | namespace liveCodeUtils { 18 | std::chrono::system_clock::time_point startTime; 19 | }; 20 | 21 | void liveCodeUtils::init() { 22 | liveCodeUtils::startTime = std::chrono::system_clock::now(); 23 | } 24 | 25 | string liveCodeUtils::execute(string cmd, int *outExitCode) { 26 | #ifndef _WIN32 27 | printf("Executing %s", cmd.c_str()); 28 | cmd += " 2>&1"; 29 | FILE *pipe = popen(cmd.c_str(), "r"); 30 | if (!pipe) return "ERROR"; 31 | char buffer[128]; 32 | std::string result = ""; 33 | while (!feof(pipe)) { 34 | if (fgets(buffer, 128, pipe) != NULL) result += buffer; 35 | } 36 | 37 | int exitCode = pclose(pipe); 38 | if (outExitCode != nullptr) { 39 | *outExitCode = WEXITSTATUS(exitCode); 40 | } 41 | printf("%s\n", result.c_str()); 42 | return result; 43 | #else 44 | return "Error - can't do this"; 45 | #endif 46 | } 47 | 48 | float liveCodeUtils::getSeconds() { 49 | auto end = std::chrono::system_clock::now(); 50 | std::chrono::duration elapsed_seconds = end - liveCodeUtils::startTime; 51 | return elapsed_seconds.count(); 52 | } 53 | 54 | vector liveCodeUtils::getAllDirectories(string baseDir) { 55 | std::vector subdirectories; 56 | 57 | // Check if the given path is a directory 58 | if (!fs::is_directory(baseDir)) { 59 | return subdirectories; 60 | } 61 | 62 | // Iterate over the directory entries recursively 63 | for (const auto &entry: fs::recursive_directory_iterator(baseDir)) { 64 | // Check if the entry is a directory 65 | if (fs::is_directory(entry)) { 66 | subdirectories.push_back(entry.path()); 67 | } 68 | } 69 | 70 | return subdirectories; 71 | } 72 | 73 | std::vector liveCodeUtils::getAllHeaderFiles(std::string baseDir) { 74 | std::vector headerFiles; 75 | 76 | // Check if the given path is a directory 77 | if (!fs::is_directory(baseDir)) { 78 | return headerFiles; 79 | } 80 | 81 | // Iterate over the directory entries recursively 82 | for (const auto &entry: fs::recursive_directory_iterator(baseDir)) { 83 | if (entry.is_regular_file()) { 84 | // Check the file extension 85 | if (entry.path().extension() == ".h" || entry.path().extension() == ".hpp") { 86 | headerFiles.push_back(entry.path().string()); 87 | } 88 | } 89 | } 90 | 91 | return headerFiles; 92 | } 93 | 94 | std::string liveCodeUtils::includeListToCFlags(const std::vector &includes) { 95 | string str; 96 | for (auto &inc: includes) { 97 | str += " -I" + inc + " "; 98 | } 99 | return str; 100 | } 101 | -------------------------------------------------------------------------------- /src/liveCodeUtils.h: -------------------------------------------------------------------------------- 1 | // 2 | // glue.h 3 | // example-simple 4 | // 5 | // Created by Marek Bereza on 16/10/2019. 6 | // 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | namespace fs = std::filesystem; 15 | 16 | namespace liveCodeUtils { 17 | void init(); 18 | std::string execute(std::string cmd, int *outExitCode = nullptr); 19 | float getSeconds(); 20 | std::string getCwd(); 21 | 22 | // this lists all directories and their subdirectories 23 | std::vector getAllDirectories(std::string baseDir); 24 | std::string includeListToCFlags(const std::vector &includes); 25 | 26 | // this returns a list of paths to all .h files in the baseDir 27 | std::vector getAllHeaderFiles(std::string baseDir); 28 | }; // namespace liveCodeUtils 29 | -------------------------------------------------------------------------------- /src/ofxCppSketch.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "ofxCppSketch.h" 3 | 4 | //static ReloadableSoundStream ofxCppSketch::soundStream; 5 | 6 | // I know this is bad, basically, the shared_ptr will be owned forever and never freed, 7 | // but it's the format which oF wants. 8 | shared_ptr ofxCppSketch::getSoundStream() { 9 | static shared_ptr soundStream = nullptr; 10 | if(soundStream==nullptr) { 11 | soundStream = make_shared(); 12 | } 13 | return soundStream; 14 | } 15 | -------------------------------------------------------------------------------- /src/ofxCppSketch.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ofMain.h" 4 | #include "ReloadableClass.h" 5 | #include "ReloadableSoundStream.h" 6 | /** 7 | * To make this work on xcode 11, you need to change a setting in your build settings, called "Dead code stripping" - change it to NO 8 | */ 9 | 10 | class ofxCppSketch : public ofBaseApp { 11 | public: 12 | ofxCppSketch(string className, string mainFilePath) 13 | : ofBaseApp() { 14 | this->className = className; 15 | srcDir = std::filesystem::path(mainFilePath).parent_path(); 16 | } 17 | 18 | void setup() override { 19 | reloader.reloadStarted = [this]() { 20 | getSoundStream()->audioMutex.lock(); 21 | getSoundStream()->inCallback = nullptr; 22 | getSoundStream()->outCallback = nullptr; 23 | }; 24 | reloader.reloaded = [this](ofBaseApp *app) { 25 | liveApp = app; 26 | liveApp->setup(); 27 | getSoundStream()->audioMutex.unlock(); 28 | }; 29 | 30 | // the only thing you have to change in settings is "Dead Code Stripping" 31 | 32 | auto pathToLiveFile = srcDir / (className + ".h"); 33 | 34 | auto includes = getAllIncludes(); 35 | 36 | // start the auto-reloading 37 | auto headerToPrecompile = getOfLibPath() / "ofMain.h"; 38 | reloader.init(pathToLiveFile.string(), includes, headerToPrecompile.string()); 39 | 40 | // watch any other header files for changes, but not any .cpp files 41 | // because that's too complicated for now 42 | auto otherHeadersInSrc = liveCodeUtils::getAllHeaderFiles(srcDir.string()); 43 | for (auto header: otherHeadersInSrc) { 44 | reloader.addFileToWatch(header); 45 | } 46 | } 47 | 48 | // I know this is bad, basically, the shared_ptr will be owned forever and never freed, 49 | // but it's the format which oF wants. 50 | static shared_ptr getSoundStream(); 51 | 52 | protected: 53 | std::filesystem::path getLibsPath() { return srcDir.parent_path() / OF_PATH / "libs"; } 54 | std::filesystem::path getAddonsPath() { return srcDir.parent_path() / OF_PATH / "addons"; } 55 | 56 | std::filesystem::path getOfLibPath() { return getLibsPath() / "openFrameworks"; } 57 | 58 | /** 59 | * returns a list of all includes needed to compile the live-coded ofApp 60 | */ 61 | vector getAllIncludes() { 62 | // add the core oF includes 63 | 64 | auto libsPathStr = getLibsPath().string(); 65 | 66 | vector includes = liveCodeUtils::getAllDirectories(getOfLibPath().string()); 67 | 68 | // add dependencies 69 | includes.push_back(libsPathStr + "/rtAudio/include"); 70 | includes.push_back(libsPathStr + "/fmodex/include"); 71 | includes.push_back(libsPathStr + "/freetype/include"); 72 | includes.push_back(libsPathStr + "/boost/include"); 73 | 74 | includes.push_back(libsPathStr + "/cairo/include/cairo"); 75 | includes.push_back(libsPathStr + "/curl/include"); 76 | includes.push_back(libsPathStr + "/json/include"); 77 | includes.push_back(libsPathStr + "/tess2/include"); 78 | 79 | includes.push_back(libsPathStr + "/glew/include"); 80 | includes.push_back(libsPathStr + "/pugixml/include"); 81 | includes.push_back(libsPathStr + "/glfw/include"); 82 | includes.push_back(libsPathStr + "/uriparser/include"); 83 | 84 | includes.push_back(libsPathStr + "/glm/include"); 85 | includes.push_back(libsPathStr + "/FreeImage/include"); 86 | includes.push_back(libsPathStr + "/utf8/include"); 87 | 88 | includes.push_back(srcDir.string()); 89 | 90 | auto addons = getAddonNames(); 91 | 92 | for (const auto &addon: addons) { 93 | auto addonPath = getAddonsPath() / addon; 94 | vector allAddonIncludes = getAllIncludeDirsForAddon(addonPath); 95 | includes.insert(includes.end(), allAddonIncludes.begin(), allAddonIncludes.end()); 96 | } 97 | 98 | return includes; 99 | } 100 | 101 | bool isIncludePath(string path) { 102 | int incPos = path.rfind("/include"); 103 | return (incPos == path.size() - 8) || (incPos == path.size() - 9 && path[path.size() - 1] == '/'); 104 | } 105 | 106 | vector getAllIncludeDirsForAddon(const std::filesystem::path &addonPath) { 107 | vector includes; 108 | includes.push_back((addonPath / "src").string()); 109 | // get all dirs recursively 110 | auto allDirs = liveCodeUtils::getAllDirectories(addonPath.c_str()); 111 | for (const auto &dir: allDirs) { 112 | if (isIncludePath(dir)) { 113 | includes.push_back(dir); 114 | } 115 | } 116 | 117 | return includes; 118 | } 119 | 120 | vector getAddonNames() { 121 | // this reads the addons.make file for adding 122 | // addon paths to the includes 123 | std::ifstream infile((srcDir.parent_path() / "addons.make").string()); 124 | vector addons; 125 | std::string line; 126 | while (std::getline(infile, line)) { 127 | if (line.size() > 3 /*&& line!="ofxCppSketch"*/) { // check there's some text on the line 128 | addons.push_back(line); 129 | printf("%s\n", line.c_str()); 130 | } 131 | } 132 | return addons; 133 | } 134 | 135 | void update() override { 136 | reloader.checkForChanges(); 137 | if (liveApp) liveApp->update(); 138 | } 139 | 140 | void draw() override { 141 | if (liveApp) liveApp->draw(); 142 | } 143 | 144 | void keyPressed(int key) override { 145 | if (liveApp) liveApp->keyPressed(key); 146 | } 147 | 148 | void keyReleased(int key) override { 149 | if (liveApp) liveApp->keyReleased(key); 150 | } 151 | 152 | void mouseMoved(int x, int y) override { 153 | if (liveApp) liveApp->mouseMoved(x, y); 154 | } 155 | 156 | void mouseDragged(int x, int y, int button) override { 157 | if (liveApp) liveApp->mouseDragged(x, y, button); 158 | } 159 | 160 | void mousePressed(int x, int y, int button) override { 161 | if (liveApp) liveApp->mousePressed(x, y, button); 162 | } 163 | 164 | void mouseReleased(int x, int y, int button) override { 165 | if (liveApp) liveApp->mouseReleased(x, y, button); 166 | } 167 | 168 | void mouseEntered(int x, int y) override { 169 | if (liveApp) liveApp->mouseEntered(x, y); 170 | } 171 | 172 | void mouseExited(int x, int y) override { 173 | if (liveApp) liveApp->mouseExited(x, y); 174 | } 175 | 176 | void windowResized(int w, int h) override { 177 | if (liveApp) liveApp->windowResized(w, h); 178 | } 179 | 180 | void dragEvent(ofDragInfo dragInfo) override { 181 | if (liveApp) liveApp->dragEvent(dragInfo); 182 | } 183 | 184 | void gotMessage(ofMessage msg) override { 185 | if (liveApp) liveApp->gotMessage(msg); 186 | } 187 | 188 | private: 189 | ReloadableClass reloader; 190 | ofBaseApp *liveApp = nullptr; 191 | string className; 192 | std::filesystem::path srcDir; 193 | std::filesystem::path OF_PATH = "../../.."; 194 | }; 195 | -------------------------------------------------------------------------------- /src/ofxCppSketchSoundStream.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // ofxCppSketchSoundStream.h 3 | // deathstortion 4 | // 5 | // Created by Marek Bereza on 01/11/2019. 6 | // 7 | 8 | #include "ofxCppSketchSoundStream.h" 9 | #include "ofxCppSketch.h" 10 | 11 | ofxCppSketchSoundStream::ofxCppSketchSoundStream() 12 | : ofSoundStream() { 13 | setSoundStream(ofxCppSketch::getSoundStream()); 14 | } 15 | -------------------------------------------------------------------------------- /src/ofxCppSketchSoundStream.h: -------------------------------------------------------------------------------- 1 | // 2 | // ofxCppSketchSoundStream.h 3 | // deathstortion 4 | // 5 | // Created by Marek Bereza on 01/11/2019. 6 | // 7 | 8 | #pragma once 9 | #include "ofMain.h" 10 | 11 | class ofxCppSketchSoundStream : public ofSoundStream { 12 | public: 13 | ofxCppSketchSoundStream(); 14 | }; 15 | --------------------------------------------------------------------------------