Create Interfase on HISE to be used on JUCE
-
@Christoph-Hart said in Create Interfase on HISE to be used on JUCE:
create a wrapper for the code around it and then compile HISE with the node.
Any clue on how to wrap this simple one oscillator C++ example into a node??? Or any node-wrapping tutorial/example?
/* ------------------------------------------------------------ name: "sine" Code generated with Faust 2.23.8 (https://faust.grame.fr) Compilation options: -lang cpp -scal -ftz 0 ------------------------------------------------------------ */ #ifndef __mydsp_H__ #define __mydsp_H__ #ifndef FAUSTFLOAT #define FAUSTFLOAT float #endif #include <algorithm> #include <cmath> #include <math.h> class mydspSIG0 { private: int iRec0[2]; public: int getNumInputsmydspSIG0() { return 0; } int getNumOutputsmydspSIG0() { return 1; } int getInputRatemydspSIG0(int channel) { int rate; switch ((channel)) { default: { rate = -1; break; } } return rate; } int getOutputRatemydspSIG0(int channel) { int rate; switch ((channel)) { case 0: { rate = 0; break; } default: { rate = -1; break; } } return rate; } void instanceInitmydspSIG0(int sample_rate) { for (int l0 = 0; (l0 < 2); l0 = (l0 + 1)) { iRec0[l0] = 0; } } void fillmydspSIG0(int count, float* table) { for (int i = 0; (i < count); i = (i + 1)) { iRec0[0] = (iRec0[1] + 1); table[i] = std::sin((9.58738019e-05f * float((iRec0[0] + -1)))); iRec0[1] = iRec0[0]; } } }; static mydspSIG0* newmydspSIG0() { return (mydspSIG0*)new mydspSIG0(); } static void deletemydspSIG0(mydspSIG0* dsp) { delete dsp; } static float ftbl0mydspSIG0[65536]; #ifndef FAUSTCLASS #define FAUSTCLASS mydsp #endif #ifdef __APPLE__ #define exp10f __exp10f #define exp10 __exp10 #endif class mydsp : public dsp { private: int fSampleRate; float fConst0; float fRec1[2]; public: void metadata(Meta* m) { m->declare("filename", "sine.dsp"); m->declare("math.lib/author", "GRAME"); m->declare("math.lib/copyright", "GRAME"); m->declare("math.lib/deprecated", "This library is deprecated and is not maintained anymore. It will be removed in August 2017."); m->declare("math.lib/license", "LGPL with exception"); m->declare("math.lib/name", "Math Library"); m->declare("math.lib/version", "1.0"); m->declare("music.lib/author", "GRAME"); m->declare("music.lib/copyright", "GRAME"); m->declare("music.lib/deprecated", "This library is deprecated and is not maintained anymore. It will be removed in August 2017."); m->declare("music.lib/license", "LGPL with exception"); m->declare("music.lib/name", "Music Library"); m->declare("music.lib/version", "1.0"); m->declare("name", "sine"); } virtual int getNumInputs() { return 0; } virtual int getNumOutputs() { return 1; } virtual int getInputRate(int channel) { int rate; switch ((channel)) { default: { rate = -1; break; } } return rate; } virtual int getOutputRate(int channel) { int rate; switch ((channel)) { case 0: { rate = 1; break; } default: { rate = -1; break; } } return rate; } static void classInit(int sample_rate) { mydspSIG0* sig0 = newmydspSIG0(); sig0->instanceInitmydspSIG0(sample_rate); sig0->fillmydspSIG0(65536, ftbl0mydspSIG0); deletemydspSIG0(sig0); } virtual void instanceConstants(int sample_rate) { fSampleRate = sample_rate; fConst0 = (440.0f / std::min<float>(192000.0f, std::max<float>(1.0f, float(fSampleRate)))); } virtual void instanceResetUserInterface() { } virtual void instanceClear() { for (int l1 = 0; (l1 < 2); l1 = (l1 + 1)) { fRec1[l1] = 0.0f; } } virtual void init(int sample_rate) { classInit(sample_rate); instanceInit(sample_rate); } virtual void instanceInit(int sample_rate) { instanceConstants(sample_rate); instanceResetUserInterface(); instanceClear(); } virtual mydsp* clone() { return new mydsp(); } virtual int getSampleRate() { return fSampleRate; } virtual void buildUserInterface(UI* ui_interface) { ui_interface->openVerticalBox("sine"); ui_interface->closeBox(); } virtual void compute(int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) { FAUSTFLOAT* output0 = outputs[0]; for (int i = 0; (i < count); i = (i + 1)) { fRec1[0] = (fConst0 + (fRec1[1] - std::floor((fConst0 + fRec1[1])))); output0[i] = FAUSTFLOAT(ftbl0mydspSIG0[int((65536.0f * fRec1[0]))]); fRec1[1] = fRec1[0]; } } }; #endif
-
@hisefilo A couple years back, Christoph suggested I look at his MDA effect wrapper for inspiration.
check outHISE-scriptnode\hi_modules\effects
-
@dustbro thanks mate!! will take a look (C++ still being a pain for me)
-
Hmm, I think it's a little bit over your head to make a proper wrapper for that code (also I think I need to tinker with the compiler flags to see if I can change the code a little bit as there are a few annoying things in it). The most promising solution would be if I just create a wrapper node for faust code that you then can use.
-
@Christoph-Hart said in Create Interfase on HISE to be used on JUCE:
The most promising solution would be if I just create a wrapper node for faust code that you then can use.
That would be awesome and a way to many faust users to get into HISE (Faust lacks of GUI designer)
JOS will be so happy if that happens :)
This is the faust2juce script if it helps#! /bin/bash -e ##################################################################### # # # Compiles Faust programs to JUCE standalone or plugin # # (c) Grame, 2016-2019 # # # ##################################################################### . faustpath . faustoptflags . usage.sh CXXFLAGS=$MYGCCFLAGS DEBUG=false #------------------------------------------------------------------- #PHASE 1 : dispatch command arguments #------------------------------------------------------------------- OSCLIB="" POLY="POLY" DEF="" EFFECT="" NVOICES=-1 STANDALONE="0" IS_SYNTH="0" IS_MIDI="0" IS_LLVM="0" JUCE_POLY="0" FAUSTFLOAT="float" JUCER="" JUCE_MODULES_DIR="\.\.\/\.\.\/modules" # By default, the generated folder is supposed to be copied in JUCE/examples folder while [ $1 ] do p=$1 if [ $p = "--help" ] || [ $p = "-help" ] || [ $p = "-h" ]; then usage faust2juce "[options] [Faust options] <file.dsp>" require Juce echo "Compiles Faust programs to JUCE standalone or plugin." option options -osc -midi -soundfile option "-nvoices <num>" option "-effect <effect.dsp>" option "-effect auto" option -standalone "to produce a standalone project, otherwise a plugin project is generated" option -"jucemodulesdir <folder>" "to set JUCE modules directory to <folder>, such as ~/JUCE/modules" option -jsynth "to use JUCE polyphonic Synthesizer instead of Faust polyphonic code" option -llvm "to use the LLVM compilation chain (OSX and Linux for now)" exit fi if [ "$p" = -debug ]; then DEBUG=true elif [ $p = "-nvoices" ]; then shift NVOICES=$1 elif [ $p = "-standalone" ]; then STANDALONE="1" elif [ $p = "-effect" ]; then DEF+="POLY2 " POLY="POLY2" shift EFFECT=$1 elif [ $p = "-jucemodulesdir" ]; then shift JUCE_MODULES_DIR=$(echo $1 | sed -e 's/\//\\\//g') elif [ $p = "-jsynth" ]; then DEF+="JUCE_POLY " JUCE_POLY="1" elif [ $p = "-midi" ]; then DEF+="MIDICTRL " IS_MIDI="1" elif [ $p = "-osc" ]; then DEF+="OSCCTRL " elif [ $p = "-llvm" ]; then IS_LLVM="1" elif [ $p = "-soundfile" ]; then DEF+="SOUNDFILE " elif [ ${p:0:1} = "-" ]; then if [ $p = "-double" ]; then FAUSTFLOAT="double" fi OPTIONS="$OPTIONS $p" elif [[ -f "$p" ]]; then FILES="$FILES $p" else OPTIONS="$OPTIONS $p" fi shift done # configure JUCER file if [ $STANDALONE = "1" ]; then if [ $IS_LLVM = "1" ]; then JUCER="standalone-llvm.jucer" else JUCER="standalone.jucer" fi else if [ $IS_LLVM = "1" ]; then JUCER="plugin-llvm.jucer" else JUCER="plugin.jucer" fi fi if [ $STANDALONE = "1" ] && [ $JUCE_POLY = "1" ]; then echo "Cannot use -standalone with -jsynth" exit fi if [ $POLY = "POLY2" ] && [ $JUCE_POLY = "1" ]; then echo "Cannot use -effect with -jsynth" exit fi #look for polyphonic "nvoices" metadata in the DSP file grep "declare nvoices" $FILES && IS_SYNTH="1" 2>/dev/null #------------------------------------------------------------------- # compile the *.dsp files #------------------------------------------------------------------- for p in $FILES; do CUR=$(pwd) f=$(basename "$p") SRCDIR=$(dirname "$p") # creates the dir dspName="${f%.dsp}" SUB_TYPE=$(shasum $p) SUB_TYPE=${SUB_TYPE:0:4} rm -rf "$SRCDIR/$dspName" if [ $STANDALONE = "0" ]; then cp -r $FAUSTARCH/juce/plugin "$SRCDIR/$dspName/" # setting plugin name to match the dsp sed -e "s/SUB_TYPE/$SUB_TYPE/g" "$SRCDIR/$dspName/$JUCER" >> "$SRCDIR/$dspName/$dspName-temp.jucer" else cp -r $FAUSTARCH/juce/standalone "$SRCDIR/$dspName/" # setting project name to match the dsp sed -e "s/ProjectTitle/$dspName/g" "$SRCDIR/$dspName/$JUCER" >> "$SRCDIR/$dspName/$dspName-temp.jucer" fi # setting the preprocessing definitions sed -e "s/PreProcDef/$DEF/g" "$SRCDIR/$dspName/$dspName-temp.jucer" >> "$SRCDIR/$dspName/$dspName-temp0.jucer" sed -e "s/APPL_NAME/$dspName/g" "$SRCDIR/$dspName/$dspName-temp0.jucer" >> "$SRCDIR/$dspName/$dspName-temp1.jucer" # MIDI sed -e "s/IS_MIDI/$IS_MIDI/g" "$SRCDIR/$dspName/$dspName-temp1.jucer" >> "$SRCDIR/$dspName/$dspName-temp2.jucer" # SYNTH sed -e "s/IS_SYNTH/$IS_SYNTH/g" "$SRCDIR/$dspName/$dspName-temp2.jucer" >> "$SRCDIR/$dspName/$dspName-temp3.jucer" # FAUSTFLOAT sed -e "s/FAUST_FLOAT/$FAUSTFLOAT/g" "$SRCDIR/$dspName/$dspName-temp3.jucer" >> "$SRCDIR/$dspName/$dspName-temp4.jucer" # JUCE_MODULES_DIR sed -e "s/JUCE_MODULES_DIR_DEFAULT/$JUCE_MODULES_DIR/g" "$SRCDIR/$dspName/$dspName-temp4.jucer" >> "$SRCDIR/$dspName/$dspName-temp5.jucer" # possibly set NVOICES value if [ $NVOICES -ge 0 ]; then sed -e "s/NUM_VOICES/$NVOICES/g" "$SRCDIR/$dspName/$dspName-temp5.jucer" >> "$SRCDIR/$dspName/$dspName.jucer" else cp "$SRCDIR/$dspName/$dspName-temp5.jucer" "$SRCDIR/$dspName/$dspName.jucer" fi # standalone or plugin mode if [ $STANDALONE = "0" ]; then rm "$SRCDIR/$dspName/plugin-llvm.jucer" "$SRCDIR/$dspName/plugin.jucer" else rm "$SRCDIR/$dspName/standalone-llvm.jucer" "$SRCDIR/$dspName/standalone.jucer" fi rm "$SRCDIR/$dspName/$dspName-temp.jucer" "$SRCDIR/$dspName/$dspName-temp0.jucer" rm "$SRCDIR/$dspName/$dspName-temp1.jucer" "$SRCDIR/$dspName/$dspName-temp2.jucer" rm "$SRCDIR/$dspName/$dspName-temp3.jucer" "$SRCDIR/$dspName/$dspName-temp4.jucer" rm "$SRCDIR/$dspName/$dspName-temp5.jucer" # standalone of plugin mode if [ $STANDALONE = "0" ]; then if [ $IS_LLVM = "1" ]; then faust -inj $FAUSTINC/faust/dsp/llvm-dsp-adapter.h -uim -i -a $FAUSTARCH/juce/juce-plugin.cpp $OPTIONS "$SRCDIR/$f" -o "$SRCDIR/$dspName/FaustPluginProcessor.cpp" || exit dynamic-faust $OPTIONS "$SRCDIR/$f" -o "$SRCDIR/$dspName/dynamic.o" else faust -uim -i -a $FAUSTARCH/juce/juce-plugin.cpp $OPTIONS "$SRCDIR/$f" -o "$SRCDIR/$dspName/FaustPluginProcessor.cpp" || exit fi else if [ $IS_LLVM = "1" ]; then faust -inj $FAUSTINC/faust/dsp/llvm-dsp-adapter.h -i -a $FAUSTARCH/juce/juce-standalone.cpp $OPTIONS "$SRCDIR/$f" -o "$SRCDIR/$dspName/FaustAudioApplication.cpp" || exit dynamic-faust $OPTIONS "$SRCDIR/$f" -o "$SRCDIR/$dspName/dynamic.o" else faust -i -a $FAUSTARCH/juce/juce-standalone.cpp $OPTIONS "$SRCDIR/$f" -o "$SRCDIR/$dspName/FaustAudioApplication.cpp" || exit fi fi if [ $POLY = "POLY2" ]; then if [ $EFFECT = "auto" ]; then cat > $SRCDIR/$dspName/effect.dsp << EndOfCode adapt(1,1) = _; adapt(2,2) = _,_; adapt(1,2) = _ <: _,_; adapt(2,1) = _,_ :> _; adaptor(F,G) = adapt(outputs(F),inputs(G)); process = adaptor(library("$SRCDIR/$f").process, library("$SRCDIR/$f").effect) : library("$SRCDIR/$f").effect; EndOfCode faust -i -cn effect -a minimal-effect.cpp "$SRCDIR/$dspName/effect.dsp" -o "$SRCDIR/$dspName/effect.h" || exit rm "$SRCDIR/$dspName/effect.dsp" else faust -i -cn effect -a minimal-effect.cpp "$SRCDIR/$EFFECT" -o "$SRCDIR/$dspName/effect.h" || exit fi fi done
-
@hisefilo Just to understand, what is the benefit of using Faust?
For what I see, everything can be made directly with scriptnode/snex right?
Easier than snex maybe? -
@ustk Faust is less buggy :p
But I've stopped playing with Faust for now because I'd rather use the new (less buggy?) version of SNEX when it's ready.
-
@ustk it's being developed for years and a huge library is there (Many of them developed by JOS)
i.e. you may have an analog modelled filter in just a few lines of code -
@d-healey I haven't realised yet what SNEX will allow me to do. C++ is kinda obscure zone for me.
Faust does the compile thing to turn this one line thingy into a physically modelled trumpet on C++
process = pm.trumpet <: _,_;
-
@Christoph-Hart said in Create Interfase on HISE to be used on JUCE:
The most promising solution would be if I just create a wrapper node for faust code that you then can use.
Maybe one for my Matlab models too? ;)
-
@hisefilo I played around with those physical models, that particular function though is just a wrapper around the STK. It's good for demonstration but you don't have any control over the components.
I rebuilt the Faust pm.flute inside Faust so that I could control each element and change the bits I didn't like, it wasn't one line :p I want to rebuild it in HISE at some point.
-
@d-healey said in Create Interfase on HISE to be used on JUCE:
I want to rebuild it in HISE at some point.
Yeah, me too.
-
@d-healey Do you know if SNEX will require C++ knowledge for creating custom stuff?? Like an Karplus-Strong modified algo or an oscillator able to read data tables?
-
-
@d-healey said in Create Interfase on HISE to be used on JUCE:
@hisefilo https://docs.hise.audio/scriptnode/manual/snex.html
Yes, being there. Needs a lot of C++ skills
-
@hisefilo It's very simplified though, doesn't look much worse than Javascript
for(auto& sample: block) { sample = (float)Math.sin(uptime); uptime += 0.002; }
The only bit there I don't really understand is
auto&
but I'm sure it's not too tricky to figure out. -
This doc is slightly outdated as there have been many additions to SNEX. Basically SNEX is C++ minus all stuff that you don't need for DSP:
- String operations (there's even not a text data type)
- File I/O or other OS stuff
- inheritance and constructor / destructors of objects
- pointer syntax, so any reference to memory is being done via the reference operator
&
. I chose to do this because it's much harder to create a null reference using this approach which makes the language a bit more stable (especially in a prototyping context).
However I would say that it's halfway between HiseScript and C++. Sure you have to use proper typing (in HiseScript / Javascript it doesn't matter if a number is a integer or floating point number, but since SNEX directly creates machine code, it needs strict typing to emit the correct assembly.
var x = 12 + 0.5; // OK in HiseScript float x = 12 + 0.5; // type mismatch, will print a warning and cast implicitely to float.
The example that David posted is trivially portable to HiseScript:
for(auto& sample: block) { sample = (float)Math.sin(uptime); uptime += 0.002; } // in HiseScript: for(sample in block) { sample = Math.sin(uptime); uptime += 0.002; }
The
Math
class is almost 100% identical in SNEX and HiseScript so this eases the pain a bit :)
About theauto&
: In SNEX you have the same range-based for loop as in Javascript (give it an array and it will iterate from the start to the end). However since it's strictly typed, you need to declare the element type of theblock
array that you want to iterate.auto
is just a way of letting the compiler figure out the type itself, so it removes redundancy:auto x = 12; // x is an int; int x = 12; // same statement but you have to figure out the type yourself. auto y = 12.0f; // x is a float (the f postfix indicates single precision)
Now the
&
operator means "reference to" and tells the compiler to operate on the oringal data itself. Outside a loop it can be used like this:int x = 126; auto& refX = x; refX = 100; // x is also 100 now auto noRef = x; // omit the & makes a copy noRef = 9000; // is still 100
Now if you write the loop without the
&
like this:for(auto sample: block) { sample = (float)Math.sin(uptime); uptime += 0.002; }
it is still valid C++ / SNEX syntax, but it will not have an effect because the
sample
variable will not point to the actual element in the array, but a copy of the value (likenoRef
in the example above), so the sine wave is being rendered into nirvana. -
@Christoph-Hart Thanks you!!!!!
well! I guess I started learning SNEX now!
I'm on January Scriptnode. Should I find a new working commit ? (MacOS) -
@hisefilo Wait for HISE 3 :D
-
Yeah I can‘t recommend diving into SNEX at the moment (especially on macOS), I‘m afraid you need to wait a few more weeks.