├── docs ├── 00.png ├── 01.png ├── 02.png ├── 03.png └── 04.png ├── app ├── assets │ ├── startup.zip │ ├── images │ │ ├── icon.png │ │ ├── icon.xcf │ │ ├── senos.icns │ │ ├── senos.ico │ │ └── senos.png │ └── app.rc.in ├── AboutWindow.hpp ├── instrument │ ├── TB303Window.hpp │ ├── SynthMachineWindow.hpp │ ├── Dx7Window.hpp │ ├── DrumMachineWindow.hpp │ ├── TB303Window.cpp │ └── DrumMachineWindow.cpp ├── tools │ ├── VUMeterWindow.hpp │ ├── DebugWindow.hpp │ ├── ScopeWindow.hpp │ ├── KeyboardWindow.hpp │ ├── ChainerWindow.hpp │ ├── SequencerWindow.hpp │ ├── VUMeterWindow.cpp │ └── ScopeWindow.cpp ├── SettingsWindow.hpp ├── Menu.hpp ├── Platform.hpp ├── AboutWindow.cpp ├── ConfigurationMigrations.cpp ├── App.hpp ├── Configuration.hpp └── Window.hpp ├── songs ├── take on me_-_04-04-2023_20.00.34.zip ├── benny benassi_-_12-04-2023_20.28.11.zip └── simple tunes_-_04-04-2023_20.12.13.zip ├── engine ├── instrument │ ├── tb303 │ │ ├── rosic_RealFunctions.h │ │ ├── rosic_EllipticQuarterBandFilter.cpp │ │ ├── rosic_AcidPattern.cpp │ │ ├── rosic_MidiNoteEvent.cpp │ │ ├── rosic_DecayEnvelope.cpp │ │ ├── rosic_Complex.cpp │ │ ├── rosic_MidiNoteEvent.h │ │ ├── rosic_BlendOscillator.cpp │ │ ├── rosic_AcidSequencer.cpp │ │ ├── rosic_LeakyIntegrator.cpp │ │ ├── rosic_LeakyIntegrator.h │ │ ├── GlobalDefinitions.h │ │ ├── rosic_TeeBeeFilter.cpp │ │ ├── rosic_DecayEnvelope.h │ │ ├── rosic_BiquadFilter.h │ │ ├── rosic_EllipticQuarterBandFilter.h │ │ ├── rosic_OnePoleFilter.cpp │ │ └── rosic_OnePoleFilter.h │ ├── dx7 │ │ ├── tuning.h │ │ ├── patch.h │ │ ├── freqlut.h │ │ ├── tuning.cc │ │ ├── module.h │ │ ├── lfo.h │ │ ├── synth.h │ │ ├── pitchenv.h │ │ ├── fm_core.h │ │ ├── fm_op_kernel.h │ │ ├── freqlut.cc │ │ ├── sin.h │ │ ├── patch.cc │ │ ├── exp2.cc │ │ ├── exp2.h │ │ ├── env.h │ │ ├── dx7note.h │ │ ├── lfo.cc │ │ ├── pitchenv.cc │ │ ├── controllers.h │ │ └── banks │ │ │ └── Banks.hpp │ ├── TB303.hpp │ ├── DrumMachine.hpp │ ├── synthmachine │ │ ├── Envelope.hpp │ │ ├── Value.hpp │ │ ├── Filter.hpp │ │ ├── Oscillator.hpp │ │ ├── Value.cpp │ │ └── Envelope.cpp │ ├── Dx7.hpp │ └── SynthMachine.hpp ├── audio │ ├── Wav.hpp │ ├── Recorder.hpp │ ├── Analyser.hpp │ ├── Midi.hpp │ ├── CircularBuffer.hpp │ ├── RunningStats.hpp │ ├── Easing.hpp │ ├── Recorder.cpp │ ├── Audio.hpp │ ├── Easing.cpp │ ├── Audio.cpp │ └── Analyser.cpp ├── Chainer.hpp ├── core │ ├── Worker.hpp │ ├── Log.hpp │ └── Worker.cpp ├── Engine.hpp ├── Sequencer.hpp └── Chainer.cpp ├── .gitignore ├── vendor ├── sokol │ └── sokol.cpp ├── imgui │ └── LICENSE.txt ├── libremidi │ ├── detail │ │ ├── midi_queue.hpp │ │ ├── semaphore.hpp │ │ ├── emscripten_api.hpp │ │ └── dummy.hpp │ ├── api.hpp │ ├── writer.hpp │ └── reader.hpp ├── imgui-knobs │ └── imgui-knobs.h ├── microtar │ └── microtar.h ├── imgui-fonts │ └── fonts.h └── miniz │ └── miniz.hpp ├── LICENSE ├── .vscode ├── launch.json └── settings.json └── README.md /docs/00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RuiVarela/Senos/HEAD/docs/00.png -------------------------------------------------------------------------------- /docs/01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RuiVarela/Senos/HEAD/docs/01.png -------------------------------------------------------------------------------- /docs/02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RuiVarela/Senos/HEAD/docs/02.png -------------------------------------------------------------------------------- /docs/03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RuiVarela/Senos/HEAD/docs/03.png -------------------------------------------------------------------------------- /docs/04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RuiVarela/Senos/HEAD/docs/04.png -------------------------------------------------------------------------------- /app/assets/startup.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RuiVarela/Senos/HEAD/app/assets/startup.zip -------------------------------------------------------------------------------- /app/assets/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RuiVarela/Senos/HEAD/app/assets/images/icon.png -------------------------------------------------------------------------------- /app/assets/images/icon.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RuiVarela/Senos/HEAD/app/assets/images/icon.xcf -------------------------------------------------------------------------------- /app/assets/images/senos.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RuiVarela/Senos/HEAD/app/assets/images/senos.icns -------------------------------------------------------------------------------- /app/assets/images/senos.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RuiVarela/Senos/HEAD/app/assets/images/senos.ico -------------------------------------------------------------------------------- /app/assets/images/senos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RuiVarela/Senos/HEAD/app/assets/images/senos.png -------------------------------------------------------------------------------- /songs/take on me_-_04-04-2023_20.00.34.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RuiVarela/Senos/HEAD/songs/take on me_-_04-04-2023_20.00.34.zip -------------------------------------------------------------------------------- /engine/instrument/tb303/rosic_RealFunctions.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RuiVarela/Senos/HEAD/engine/instrument/tb303/rosic_RealFunctions.h -------------------------------------------------------------------------------- /songs/benny benassi_-_12-04-2023_20.28.11.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RuiVarela/Senos/HEAD/songs/benny benassi_-_12-04-2023_20.28.11.zip -------------------------------------------------------------------------------- /songs/simple tunes_-_04-04-2023_20.12.13.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RuiVarela/Senos/HEAD/songs/simple tunes_-_04-04-2023_20.12.13.zip -------------------------------------------------------------------------------- /app/AboutWindow.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Window.hpp" 4 | 5 | namespace sns { 6 | 7 | class AboutWindow : public Window { 8 | public: 9 | AboutWindow(); 10 | ~AboutWindow() override; 11 | 12 | void render() override; 13 | 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /app/instrument/TB303Window.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../Window.hpp" 4 | 5 | namespace sns { 6 | 7 | class TB303Window : public Window { 8 | public: 9 | TB303Window(); 10 | ~TB303Window() override; 11 | 12 | void render() override; 13 | protected: 14 | void renderBassLine(); 15 | 16 | }; 17 | } -------------------------------------------------------------------------------- /app/tools/VUMeterWindow.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../Window.hpp" 4 | 5 | namespace sns 6 | { 7 | class VUMeterWindow : public Window 8 | { 9 | public: 10 | VUMeterWindow(); 11 | ~VUMeterWindow() override; 12 | 13 | void render() override; 14 | 15 | private: 16 | int m_levels; 17 | 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /app/instrument/SynthMachineWindow.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../Window.hpp" 4 | 5 | namespace sns { 6 | 7 | class SynthMachineWindow : public Window { 8 | public: 9 | SynthMachineWindow(); 10 | ~SynthMachineWindow() override; 11 | 12 | void render() override; 13 | 14 | private: 15 | void renderOscillator(int index); 16 | void renderEnvelope(); 17 | void renderFilter(); 18 | void renderOptions(); 19 | }; 20 | } -------------------------------------------------------------------------------- /app/SettingsWindow.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Window.hpp" 4 | 5 | namespace sns { 6 | 7 | class SettingsWindow : public Window { 8 | public: 9 | SettingsWindow(); 10 | ~SettingsWindow() override; 11 | 12 | 13 | void show(bool value) override; 14 | void render() override; 15 | 16 | private: 17 | void renderAudio(); 18 | void renderVideo(); 19 | void rendeMidi(); 20 | 21 | std::vector m_midi_presets; 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /.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 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | build 34 | *.DS_Store 35 | .cache 36 | -------------------------------------------------------------------------------- /app/instrument/Dx7Window.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../Window.hpp" 4 | 5 | namespace sns { 6 | 7 | class Dx7Window : public Window { 8 | public: 9 | Dx7Window(); 10 | ~Dx7Window() override; 11 | 12 | void render() override; 13 | protected: 14 | void onLoadPreset(std::string const& name) override; 15 | 16 | private: 17 | void renderPatches(); 18 | void renderDynamics(); 19 | void renderOptions(); 20 | 21 | bool m_update_scrolls; 22 | }; 23 | } -------------------------------------------------------------------------------- /app/instrument/DrumMachineWindow.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../Window.hpp" 4 | 5 | #include "../../engine/instrument/DrumMachine.hpp" 6 | 7 | namespace sns { 8 | 9 | class DrumMachineWindow : public Window { 10 | public: 11 | DrumMachineWindow(); 12 | ~DrumMachineWindow() override; 13 | 14 | void render() override; 15 | protected: 16 | 17 | 18 | void renderTable(std::string const& name, std::vector const& data); 19 | void renderOptions(); 20 | }; 21 | } -------------------------------------------------------------------------------- /engine/instrument/dx7/tuning.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "synth.h" 4 | 5 | namespace dx7 { 6 | 7 | class TuningState { 8 | public: 9 | virtual ~TuningState() { } 10 | 11 | virtual int32_t midinote_to_logfreq(int midinote) = 0; 12 | virtual bool is_standard_tuning() { return true; } 13 | virtual int scale_length() { return 12; } 14 | virtual std::string display_tuning_str() { return "Standard Tuning"; } 15 | }; 16 | 17 | std::shared_ptr createStandardTuning(); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /engine/audio/Wav.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../audio/Audio.hpp" 4 | 5 | namespace sns { 6 | 7 | // 8 | // wav writer 9 | // 10 | class Wav { 11 | public: 12 | Wav(); 13 | ~Wav(); 14 | 15 | bool open(std::string const& filename); 16 | void close(); 17 | bool isOk(); 18 | int write(float* data, int frames); 19 | 20 | static std::vector load(std::string const& filename); 21 | private: 22 | struct PrivateImplementation; 23 | std::shared_ptr m; 24 | 25 | std::string m_filename; 26 | bool m_is_ok; 27 | }; 28 | 29 | } -------------------------------------------------------------------------------- /vendor/sokol/sokol.cpp: -------------------------------------------------------------------------------- 1 | // sokol implementation library on non-Apple platforms 2 | #define SOKOL_IMPL 3 | 4 | #if defined(_WIN32) 5 | #define SOKOL_D3D11 6 | #elif defined(__EMSCRIPTEN__) 7 | #define SOKOL_GLES2 8 | #elif defined(__APPLE__) 9 | // NOTE: on macOS, sokol.c is compiled explicitely as ObjC 10 | #define SOKOL_METAL 11 | #else 12 | #define SOKOL_GLCORE33 13 | #endif 14 | 15 | #include "sokol_app.h" 16 | #include "sokol_gfx.h" 17 | #include "sokol_glue.h" 18 | #include "sokol_audio.h" 19 | 20 | #define SOKOL_IMGUI_IMPL 21 | #include "../imgui/imgui.h" 22 | #include "util/sokol_imgui.h" 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/tools/DebugWindow.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../Window.hpp" 4 | 5 | namespace sns { 6 | 7 | class DebugWindow : public Window { 8 | public: 9 | DebugWindow(); 10 | ~DebugWindow() override; 11 | 12 | 13 | void initialize() override; 14 | void shutdown() override; 15 | 16 | void render() override; 17 | 18 | void clearLog(); 19 | void addLog(std::string const& message); 20 | private: 21 | struct PrivateImplementation; 22 | std::shared_ptr m; 23 | 24 | void renderLog(); 25 | 26 | void showNotesNames(); 27 | void showNotesFrequencies(); 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /engine/instrument/tb303/rosic_EllipticQuarterBandFilter.cpp: -------------------------------------------------------------------------------- 1 | #include "rosic_EllipticQuarterBandFilter.h" 2 | using namespace rosic; 3 | 4 | //------------------------------------------------------------------------------------------------- 5 | // construction/destruction: 6 | 7 | EllipticQuarterBandFilter::EllipticQuarterBandFilter() 8 | { 9 | reset(); 10 | } 11 | 12 | //------------------------------------------------------------------------------------------------- 13 | // parameter settings: 14 | 15 | void EllipticQuarterBandFilter::reset() 16 | { 17 | for(int i=0; i<12; i++) 18 | w[i] = 0.0; 19 | } 20 | 21 | -------------------------------------------------------------------------------- /engine/instrument/TB303.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Instrument.hpp" 4 | 5 | namespace sns { 6 | 7 | class TB303 : public BaseInstrument { 8 | public: 9 | TB303(); 10 | ~TB303() override; 11 | 12 | static ParametersValues defaultParameters(); 13 | void setValues(ParametersValues const& values) override; 14 | 15 | void onMidi(MidiMessage const& message) override; 16 | void setNote(int note, float velocity) override; 17 | 18 | float next() override; 19 | void panic() override; 20 | private: 21 | struct PrivateImplementation; 22 | std::unique_ptr m; 23 | void updateEnvelopModulation(); 24 | }; 25 | } -------------------------------------------------------------------------------- /app/tools/ScopeWindow.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../Window.hpp" 4 | 5 | namespace sns 6 | { 7 | class ScopeWindow : public Window 8 | { 9 | public: 10 | ScopeWindow(); 11 | ~ScopeWindow() override; 12 | 13 | void render() override; 14 | 15 | private: 16 | std::vector m_samples; 17 | 18 | void updateCapture(); 19 | 20 | // pickers 21 | bool m_active; 22 | 23 | std::vector m_time_names; 24 | std::vector m_time_values; 25 | int m_time; 26 | 27 | std::vector m_points_names; 28 | std::vector m_points_values; 29 | int m_points; 30 | 31 | std::vector m_sync_names; 32 | std::vector m_sync_values; 33 | int m_sync; 34 | 35 | float m_offset_factor; 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /app/Menu.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../engine/core/Lang.hpp" 4 | #include "../engine/core/Text.hpp" 5 | 6 | namespace sns { 7 | 8 | struct MenuItem { 9 | std::string name; 10 | std::string command; 11 | 12 | std::string shortcut; 13 | int shortcut_key; 14 | int shortcut_mod; 15 | }; 16 | 17 | struct MenuColumn { 18 | std::string name; 19 | std::vector items; 20 | 21 | inline void add(std::string const& name, 22 | int shortcut_key = 0, int shortcut_mod = 0) { 23 | items.push_back({ name, sfmt("%s|%s", this->name, name), "", shortcut_key, shortcut_mod }); 24 | } 25 | }; 26 | 27 | using Menu = std::vector; 28 | using MenuShortcuts = std::vector; 29 | using MenuCallback = std::function; 30 | 31 | } 32 | -------------------------------------------------------------------------------- /engine/instrument/dx7/patch.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* 3 | * Copyright 2013 Google Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | namespace dx7 { 18 | 19 | void UnpackPatch(const char bulk[128], char patch[156]); 20 | 21 | } 22 | 23 | -------------------------------------------------------------------------------- /app/tools/KeyboardWindow.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../Window.hpp" 4 | 5 | namespace sns { 6 | class App; 7 | 8 | class KeyboardWindow : public Window { 9 | public: 10 | 11 | enum class KeyState { 12 | Off, 13 | OnByMouse, 14 | OnByKeyboard, 15 | OnByMidi 16 | }; 17 | static std::string KeyStateName(KeyState state); 18 | 19 | KeyboardWindow(); 20 | ~KeyboardWindow(); 21 | 22 | void render() override; 23 | 24 | bool isPressed(int key); 25 | KeyState state(int key); 26 | void changePressed(int key, KeyState state, float velocity = 0.0f); 27 | 28 | void updateMidiController(std::set const& pressed); 29 | private: 30 | struct PrivateImplementation; 31 | std::shared_ptr m; 32 | 33 | void changedOctave(); 34 | void panic(); 35 | int m_octave; 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /engine/instrument/dx7/freqlut.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* 3 | * Copyright 2012 Google Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | namespace dx7 { 19 | 20 | class Freqlut { 21 | public: 22 | static void init(double sample_rate); 23 | static int32_t lookup(int32_t logfreq); 24 | }; 25 | 26 | } -------------------------------------------------------------------------------- /engine/instrument/DrumMachine.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Instrument.hpp" 4 | 5 | namespace sns { 6 | 7 | class DrumMachine : public BaseInstrument { 8 | public: 9 | DrumMachine(); 10 | ~DrumMachine() override; 11 | 12 | struct DMKey { 13 | int key; 14 | std::string name; 15 | std::string alias; 16 | }; 17 | 18 | static std::vector const& tr808(); 19 | static std::vector const& tr909(); 20 | static std::string alias(int note); 21 | 22 | static ParametersValues defaultParameters(); 23 | void setValues(ParametersValues const& values) override; 24 | 25 | void onMidi(MidiMessage const& message) override; 26 | void setNote(int note, float velocity) override; 27 | 28 | float next() override; 29 | void panic() override; 30 | private: 31 | struct PrivateImplementation; 32 | std::unique_ptr m; 33 | }; 34 | } -------------------------------------------------------------------------------- /engine/audio/Recorder.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../core/Worker.hpp" 4 | #include "Wav.hpp" 5 | 6 | namespace sns { 7 | 8 | class Recorder : private Worker { 9 | public: 10 | Recorder(); 11 | ~Recorder() override; 12 | 13 | void startRecording(std::string const& filename); 14 | void stopRecording(); 15 | bool isRecording(); 16 | 17 | uint64_t recordedMilliseconds(); 18 | 19 | bool isAccepting(); 20 | void push(float sample); 21 | 22 | protected: 23 | void workStep() override; 24 | void preWork() override; 25 | void postWork() override; 26 | 27 | private: 28 | std::string TAG; 29 | 30 | std::mutex m_mutex; 31 | std::vector m_write; 32 | std::vector m_read; 33 | size_t m_flush_size; 34 | uint64_t m_received_samples; 35 | 36 | Wav m_output; 37 | std::string m_filename; 38 | bool m_accepting; 39 | 40 | void writeToFile(); 41 | }; 42 | 43 | } -------------------------------------------------------------------------------- /app/assets/app.rc.in: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | VS_VERSION_INFO VERSIONINFO 4 | FILEVERSION @VERSION_MAJOR@, @VERSION_MINOR@, @VERSION_BUILD@ 5 | PRODUCTVERSION @VERSION_MAJOR@, @VERSION_MINOR@, @VERSION_BUILD@ 6 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK 7 | 8 | FILESUBTYPE VFT2_UNKNOWN 9 | BEGIN 10 | BLOCK "StringFileInfo" 11 | BEGIN 12 | BLOCK "04090000" 13 | BEGIN 14 | VALUE "FileDescription", "Senos audio explorer" 15 | VALUE "InternalName", "senos" 16 | VALUE "LegalCopyright", "Rui Varela" 17 | VALUE "OriginalFilename", "Senos.exe" 18 | VALUE "ProductName", "Senos" 19 | VALUE "ProductVersion", "@VERSION_MAJOR@.@VERSION_MINOR@.@VERSION_BUILD@" 20 | END 21 | END 22 | BLOCK "VarFileInfo" 23 | BEGIN 24 | VALUE "Translation", 0x409, 1200 25 | END 26 | END 27 | 28 | 29 | IDI_ICON1 ICON DISCARDABLE "senos.ico" 30 | -------------------------------------------------------------------------------- /engine/instrument/dx7/tuning.cc: -------------------------------------------------------------------------------- 1 | #include "tuning.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace dx7 { 12 | 13 | struct StandardTuning : public TuningState { 14 | StandardTuning() { 15 | const int base = 50857777; // (1 << 24) * (log(440) / log(2) - 69/12) 16 | const int step = (1 << 24) / 12; 17 | for( int mn = 0; mn < 128; ++mn ) 18 | { 19 | auto res = base + step * mn; 20 | current_logfreq_table_[mn] = res; 21 | } 22 | } 23 | 24 | virtual int32_t midinote_to_logfreq(int midinote) override { 25 | return current_logfreq_table_[midinote]; 26 | } 27 | 28 | int current_logfreq_table_[128]; 29 | }; 30 | 31 | 32 | std::shared_ptr createStandardTuning() 33 | { 34 | return std::make_shared(); 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /app/Platform.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Menu.hpp" 4 | 5 | namespace sns { 6 | std::string platformLocalFolder(std::string const& app); 7 | void platformShellOpen(std::string const& cmd); 8 | 9 | void platformPickLoadFile(std::string const& title, std::string const& message, std::string const& ext, std::function callback); 10 | void platformPickSaveFile(std::string const& title, std::string const& message, std::string const& filename, std::function callback); 11 | 12 | bool platformHasFileMenu(); 13 | void platformSetupFileMenu(Menu const& menu, MenuCallback callback); 14 | 15 | void platformSetupWindow(int min_w, int min_h); 16 | void platformFullscreenChanged(bool on); 17 | 18 | 19 | enum class PlatformEvent { 20 | Sleep, 21 | Wakeup 22 | }; 23 | using PlatformEventCallback = std::function; 24 | void platformRegisterCallback(PlatformEventCallback callback); 25 | void platformClearCallbacks(); 26 | } 27 | -------------------------------------------------------------------------------- /engine/instrument/dx7/module.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* 3 | * Copyright 2012 Google Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | #include "synth.h" 18 | 19 | namespace dx7 { 20 | 21 | class Module { 22 | public: 23 | static const int lg_n = 6; 24 | static const int n = 1 << lg_n; 25 | virtual void process(const int32_t **inbufs, const int32_t *control_in, const int32_t *control_last, int32_t **outbufs) = 0; 26 | }; 27 | 28 | } 29 | 30 | -------------------------------------------------------------------------------- /engine/Chainer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Sequencer.hpp" 4 | 5 | namespace sns { 6 | 7 | 8 | class Chainer { 9 | public: 10 | 11 | struct Link { 12 | std::string name; 13 | int runs = 0; 14 | Sequencer::Configuration sequence; 15 | bool valid = false; 16 | }; 17 | 18 | struct Configuration { 19 | Sequencer::Action action = Sequencer::Action::None; 20 | std::vector chain; 21 | bool valid = false; 22 | int action_link = 0; 23 | }; 24 | 25 | struct State { 26 | int link_index = 0; 27 | int link_run = 0; 28 | bool playing = false; 29 | }; 30 | 31 | Chainer(); 32 | Chainer(Chainer const&) = delete; 33 | Chainer& operator=(Chainer const&) = delete; 34 | 35 | void setEngine(Engine* engine); 36 | 37 | State state(); 38 | bool next(); 39 | 40 | void apply(Configuration const& cfg); 41 | void panic(); 42 | private: 43 | std::string TAG; 44 | Engine* m_engine; 45 | State m_state; 46 | Configuration m_cfg; 47 | 48 | bool advanceToNext(); 49 | }; 50 | } -------------------------------------------------------------------------------- /engine/instrument/synthmachine/Envelope.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../../audio/Audio.hpp" 4 | 5 | namespace sns { 6 | 7 | class Envelope { 8 | public: 9 | 10 | enum class Stage { 11 | NotStarted, 12 | Attack, 13 | Decay, 14 | Sustain, 15 | Release, 16 | Kill, 17 | Off 18 | }; 19 | 20 | Envelope(); 21 | 22 | void setAttack(float a); 23 | void setDecay(float d); 24 | void setSustain(float s); 25 | void setRelease(float r); 26 | 27 | void trigger(float level = 1.0f); 28 | 29 | void release(); 30 | void kill(); // signal that we need o kill this as soon as possible 31 | 32 | bool completed(); 33 | float next(); 34 | private: 35 | Stage m_stage; 36 | 37 | float m_attack_rate; 38 | float m_decay_rate; 39 | float m_sustain; 40 | float m_release_rate; 41 | float m_kill_rate; 42 | 43 | float m_current; 44 | float m_level; 45 | float m_off_level; 46 | 47 | void updateAttack(); 48 | void updateDecay(); 49 | void updateRelease(float rate); 50 | }; 51 | } -------------------------------------------------------------------------------- /engine/instrument/synthmachine/Value.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../../audio/Audio.hpp" 4 | 5 | namespace sns 6 | { 7 | 8 | class Value 9 | { 10 | public: 11 | explicit Value(float value = 0.0f); 12 | 13 | void setEasing(EasingMethod method); 14 | EasingMethod easingMethod(); 15 | 16 | 17 | void changeWithSamples(float value, uint64_t samples); 18 | void changeWithTime(float value, uint64_t ms); 19 | void changeWithIncrement(float value, float increment); 20 | 21 | float v(); 22 | float next(); 23 | 24 | void set(float value); 25 | void operator = (float value); 26 | 27 | bool changing() const; 28 | 29 | private: 30 | enum class Mode { 31 | Off, 32 | Eased, 33 | Constant 34 | }; 35 | 36 | Mode m_mode; 37 | float m_current; 38 | 39 | float m_increment; 40 | 41 | EasingMethod m_easing; 42 | float m_start; 43 | float m_range; 44 | int m_samples; 45 | int m_current_sample; 46 | 47 | }; 48 | 49 | } -------------------------------------------------------------------------------- /app/tools/ChainerWindow.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../Window.hpp" 4 | #include "../Configuration.hpp" 5 | 6 | namespace sns { 7 | 8 | class ChainerWindow : public Window { 9 | public: 10 | ChainerWindow(); 11 | ~ChainerWindow() override; 12 | 13 | void render() override; 14 | void stateChanged(Chainer::State const& state); 15 | 16 | void reloadSequences(); 17 | bool playing(); 18 | void stop(); 19 | 20 | static void inflate(sns::Configuration const& cfg, Chainer::Configuration& chainer_cfg); 21 | static void deflate(Chainer::Configuration& chainer_cfg); 22 | private: 23 | 24 | void renderTopBar(); 25 | void renderTable(float height); 26 | 27 | void onSavePreset(std::string const& name) override; 28 | void onLoadPreset(std::string const& name) override; 29 | void onDeletePreset(std::string const& name) override; 30 | void onRefreshPresets() override; 31 | 32 | int m_selected; 33 | Chainer::Configuration m_cfg; 34 | bool m_cfg_loaded; 35 | Chainer::State m_state; 36 | 37 | void sequencerStop(); 38 | 39 | std::vector m_sequence_names; 40 | }; 41 | 42 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Simon Altschuler 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /engine/instrument/Dx7.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Instrument.hpp" 4 | 5 | namespace sns 6 | { 7 | class Dx7 : public BaseInstrument 8 | { 9 | public: 10 | Dx7(); 11 | ~Dx7() override; 12 | 13 | static ParametersValues defaultParameters(); 14 | void setValues(ParametersValues const& values) override; 15 | 16 | void setNote(int note, float velocity) override; 17 | void onMidi(MidiMessage const& message) override; 18 | 19 | float next() override; 20 | void panic() override; 21 | private: 22 | struct PrivateImplementation; 23 | std::unique_ptr m; 24 | 25 | void onMidi(uint8_t const* data, int data_size); 26 | 27 | // zero-based 28 | void programChange(int p); 29 | 30 | void setController(int controller, int value); 31 | bool setSysex(const uint8_t* data, uint32_t size); 32 | void setPatch(const uint8_t* patch, uint32_t size); 33 | void setParam(uint32_t id, char value); 34 | 35 | // Choose a note for a new key-down, returns note number, or -1 if none available. 36 | void resetVoice(int v); 37 | void midiNotePressed(int midinote, int velocity); 38 | void midiNoteReleased(int midinote); 39 | }; 40 | 41 | } -------------------------------------------------------------------------------- /vendor/imgui/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2023 Omar Cornut 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /engine/instrument/synthmachine/Filter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../../audio/Audio.hpp" 4 | 5 | namespace sns { 6 | 7 | class Filter { 8 | public: 9 | 10 | enum class Kind { 11 | Off, 12 | Lowpass, 13 | Bandpass, 14 | Highpass, 15 | Notch, 16 | Peak, 17 | Count 18 | }; 19 | 20 | Filter(Kind kind = Kind::Lowpass); 21 | 22 | std::string toString() const; 23 | 24 | static std::vector kindNames(); 25 | 26 | void setKind(Kind kind); 27 | Kind kind() const; 28 | 29 | void reset(); 30 | 31 | //hz 32 | void setCutoff(float value); 33 | float cutoff() const; 34 | 35 | void setResonance(float value); 36 | float resonance(); 37 | 38 | void setDrive(float value); 39 | float drive(); 40 | 41 | float next(float input); 42 | private: 43 | struct PrivateImplementation; 44 | std::shared_ptr m; 45 | 46 | Kind m_kind; 47 | float m_cutoff; 48 | float m_resonance; 49 | float m_drive; 50 | bool m_force_update; 51 | }; 52 | 53 | std::string toString(Filter::Kind kind); 54 | 55 | } -------------------------------------------------------------------------------- /engine/audio/Analyser.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../core/Lang.hpp" 4 | #include "Audio.hpp" 5 | #include "CircularBuffer.hpp" 6 | #include "RunningStats.hpp" 7 | 8 | #include 9 | #include 10 | 11 | namespace sns { 12 | 13 | enum class AnalyserSync { 14 | None, 15 | RiseZero, 16 | FallZero 17 | }; 18 | 19 | class Analyser { 20 | public: 21 | Analyser(); 22 | 23 | void start(std::string const& key); 24 | void stop(std::string const& key); 25 | bool isAccepting(); 26 | 27 | void push(float sample); 28 | 29 | void configureGraph(int points, int duration, float offset_factor, AnalyserSync sync); 30 | void generateGraph(std::vector& points); 31 | 32 | float peak(); 33 | 34 | private: 35 | static constexpr int SamplesDuration = (int)audioMilliseconds(SampleRate); 36 | const std::string TAG = "Analyser"; 37 | AnalyserSync m_sync; 38 | 39 | 40 | int m_graph_points; // the number of graphed samples 41 | int m_graph_duration; 42 | int m_graph_duration_samples; 43 | int m_graph_offset; 44 | 45 | CircularBuffer m_samples; 46 | std::mutex m_samples_mutex; 47 | 48 | bool m_started; 49 | std::vector m_keys; 50 | }; 51 | 52 | std::string toString(AnalyserSync kind); 53 | 54 | } -------------------------------------------------------------------------------- /engine/instrument/dx7/lfo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* 3 | * Copyright 2013 Google Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | // Low frequency oscillator, compatible with DX7 19 | 20 | namespace dx7 { 21 | 22 | class Lfo { 23 | public: 24 | static void init(double sample_rate); 25 | void reset(const char params[6]); 26 | 27 | // result is 0..1 in Q24 28 | int32_t getsample(); 29 | 30 | // result is 0..1 in Q24 31 | int32_t getdelay(); 32 | 33 | void keydown(); 34 | private: 35 | static uint32_t unit_; 36 | 37 | uint32_t phase_; // Q32 38 | uint32_t delta_; 39 | uint8_t waveform_; 40 | uint8_t randstate_; 41 | bool sync_; 42 | 43 | uint32_t delaystate_; 44 | uint32_t delayinc_; 45 | uint32_t delayinc2_; 46 | }; 47 | 48 | } -------------------------------------------------------------------------------- /engine/audio/Midi.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Audio.hpp" 4 | #include "../core/Worker.hpp" 5 | #include "../instrument/Instrument.hpp" 6 | 7 | namespace sns { 8 | 9 | class Midi : public Worker { 10 | public: 11 | 12 | struct InstrummentMapping { 13 | std::string port; // MIDI physical port 14 | int channel; // Midi protocol channel 15 | 16 | std::map cc; // map of midi cc's to instrument parameter 17 | }; 18 | 19 | struct Mapping { 20 | Mapping(); 21 | 22 | std::array instruments; 23 | bool filter_active_instrument; 24 | }; 25 | 26 | Midi(); 27 | ~Midi() override; 28 | 29 | 30 | Midi& operator=(Midi const&) = delete; 31 | Midi(Midi const&) = delete; 32 | 33 | void setMapping(Mapping const& mapping); 34 | void setActiveInstrument(InstrumentId instrument); 35 | 36 | std::vector ports(); 37 | MidiMessages take(); 38 | 39 | protected: 40 | void preWork() override; 41 | void workStep() override; 42 | void postWork() override; 43 | 44 | private: 45 | struct PrivateImplementation; 46 | std::unique_ptr m; 47 | 48 | std::string TAG; 49 | 50 | void midiSetup(std::vector const& ports); 51 | void midiTeardown(); 52 | 53 | void midiEnumerate(); 54 | void midiDumpInfo(); 55 | void midiReceived(MidiMessage& message); 56 | }; 57 | 58 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "debug launch", 6 | "type": "cppdbg", 7 | "request": "launch", 8 | // Resolved by CMake Tools: 9 | "program": "${command:cmake.launchTargetPath}", 10 | "args": [], 11 | "cwd": "${workspaceFolder}/build", 12 | "console": "externalTerminal", 13 | "MIMode": "lldb" 14 | }, 15 | { 16 | "name": "(msvc) Launch", 17 | "type": "cppvsdbg", 18 | "request": "launch", 19 | // Resolved by CMake Tools: 20 | "program": "${command:cmake.launchTargetPath}", 21 | "args": [], 22 | "stopAtEntry": false, 23 | "cwd": "${workspaceFolder}", 24 | "environment": [ 25 | { 26 | // add the directory where our target was built to the PATHs 27 | // it gets resolved by CMake Tools: 28 | "name": "PATH", 29 | "value": "${env:PATH}:${command:cmake.getLaunchTargetDirectory}" 30 | }, 31 | { 32 | "name": "OTHER_VALUE", 33 | "value": "Something something" 34 | } 35 | ], 36 | "console": "externalTerminal" 37 | } 38 | ] 39 | } -------------------------------------------------------------------------------- /vendor/libremidi/detail/midi_queue.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace libremidi 6 | { 7 | 8 | struct midi_queue 9 | { 10 | unsigned int front{}; 11 | unsigned int back{}; 12 | unsigned int ringSize{}; 13 | std::unique_ptr ring{}; 14 | 15 | bool push(const message& msg) 16 | { 17 | auto [sz, _, b] = get_dimensions(); 18 | 19 | if (sz < ringSize - 1) 20 | { 21 | ring[b] = msg; 22 | back = (back + 1) % ringSize; 23 | return true; 24 | } 25 | 26 | return false; 27 | } 28 | 29 | bool pop(message& msg) 30 | { 31 | auto [sz, f, _] = get_dimensions(); 32 | 33 | if (sz == 0) 34 | { 35 | return false; 36 | } 37 | 38 | // Copy queued message to the vector pointer argument and then "pop" it. 39 | using namespace std; 40 | swap(msg, ring[f]); 41 | 42 | // Update front 43 | front = (front + 1) % ringSize; 44 | return true; 45 | } 46 | 47 | struct dimensions 48 | { 49 | unsigned int size, front, back; 50 | }; 51 | 52 | dimensions get_dimensions() const 53 | { 54 | // Access back/front members exactly once and make stack copies for 55 | // size calculation ==> completely unneccessary 56 | // https://godbolt.org/g/HPu9LA 57 | 58 | return {(back >= front) ? back - front : ringSize - front + back, front, back}; 59 | } 60 | }; 61 | 62 | } 63 | -------------------------------------------------------------------------------- /engine/instrument/dx7/synth.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #define _USE_MATH_DEFINES 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | namespace dx7 { 35 | 36 | const static int LG_N = 6; 37 | const static int N = (1 << LG_N); 38 | 39 | #define QER(n,b) ( ((float)n)/(1< 42 | class AlignedBuf { 43 | public: 44 | T *get() { 45 | return (T *)((((intptr_t)storage_) + alignment - 1) & -alignment); 46 | } 47 | private: 48 | unsigned char storage_[size * sizeof(T) + alignment]; 49 | }; 50 | 51 | } 52 | -------------------------------------------------------------------------------- /app/tools/SequencerWindow.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../Window.hpp" 4 | #include "../engine/Sequencer.hpp" 5 | 6 | namespace sns { 7 | 8 | class SequencerWindow : public Window { 9 | public: 10 | SequencerWindow(); 11 | ~SequencerWindow() override; 12 | 13 | void render() override; 14 | void stateChanged(Sequencer::State const& state); 15 | 16 | bool playing(); 17 | void stop(); 18 | private: 19 | void renderTopBar(); 20 | void renderTable(float height); 21 | void renderPopup(int step_index, int note); 22 | 23 | void onSavePreset(std::string const& name) override; 24 | void onLoadPreset(std::string const& name) override; 25 | void onDeletePreset(std::string const& name) override; 26 | void onRefreshPresets() override; 27 | 28 | void chainerStop(); 29 | bool chainerPlaying(); 30 | 31 | Sequencer::Configuration m_cfg; 32 | bool m_cfg_updated; 33 | bool m_cfg_loaded; 34 | bool m_needs_scroll; 35 | bool m_rendered_table; 36 | Sequencer::State m_state; 37 | 38 | char const* m_note_icons[int(Sequencer::NoteMode::Count)]; 39 | int m_note_keys[int(Sequencer::NoteMode::Count)]; 40 | 41 | enum class MoveKind { 42 | Up, 43 | Down, 44 | Left, 45 | Right, 46 | 47 | Count 48 | }; 49 | char const* m_move_names[int(MoveKind::Count)]; 50 | void handleInputs(int step_index, int note, Sequencer::NoteMode value); 51 | void handleMove(MoveKind kind, int step_index, int note); 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /engine/instrument/dx7/pitchenv.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* 3 | * Copyright 2013 Google Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | // Computation of the DX7 pitch envelope 19 | namespace dx7 { 20 | 21 | class PitchEnv { 22 | public: 23 | static void init(double sample_rate); 24 | 25 | // The rates and levels arrays are calibrated to match the Dx7 parameters 26 | // (ie, value 0..99). 27 | void set(const int rates[4], const int levels[4]); 28 | 29 | // Result is in Q24/octave 30 | int32_t getsample(); 31 | void keydown(bool down); 32 | void getPosition(char *step); 33 | private: 34 | static int unit_; 35 | int rates_[4]; 36 | int levels_[4]; 37 | int32_t level_; 38 | int targetlevel_; 39 | bool rising_; 40 | int ix_; 41 | int inc_; 42 | 43 | bool down_; 44 | 45 | void advance(int newix); 46 | }; 47 | 48 | extern const uint8_t pitchenv_rate[]; 49 | extern const int8_t pitchenv_tab[]; 50 | 51 | } 52 | -------------------------------------------------------------------------------- /engine/instrument/tb303/rosic_AcidPattern.cpp: -------------------------------------------------------------------------------- 1 | #include "rosic_AcidPattern.h" 2 | using namespace rosic; 3 | 4 | AcidPattern::AcidPattern() 5 | { 6 | numSteps = 16; 7 | stepLength = 0.5; 8 | } 9 | 10 | //------------------------------------------------------------------------------------------------- 11 | // setup: 12 | 13 | void AcidPattern::clear() 14 | { 15 | for(int i=0; ibuf_[2]; 51 | const static FmAlgorithm algorithms[32]; 52 | }; 53 | 54 | } -------------------------------------------------------------------------------- /engine/instrument/tb303/rosic_MidiNoteEvent.cpp: -------------------------------------------------------------------------------- 1 | #include "rosic_MidiNoteEvent.h" 2 | using namespace rosic; 3 | 4 | //------------------------------------------------------------------------------------------------- 5 | // construction/destruction: 6 | 7 | MidiNoteEvent::MidiNoteEvent() 8 | { 9 | key = 64; 10 | vel = 64; 11 | detune = 0.0; 12 | priority = 0; 13 | } 14 | 15 | MidiNoteEvent::MidiNoteEvent(int initKey, int initVel, int initDetune, int initPriority) 16 | { 17 | if( initKey >=0 && initKey <= 127) 18 | key = initKey; 19 | else 20 | key = 64; 21 | 22 | if( initVel >=0 && initVel <= 127) 23 | vel = initVel; 24 | else 25 | vel = 64; 26 | 27 | if( initPriority >=0 ) 28 | priority = initPriority; 29 | else 30 | priority = 0; 31 | 32 | detune = initDetune; 33 | } 34 | 35 | MidiNoteEvent::~MidiNoteEvent() 36 | { 37 | 38 | } 39 | 40 | //------------------------------------------------------------------------------------------------- 41 | // parameter settings: 42 | 43 | void MidiNoteEvent::setKey(int newKey) 44 | { 45 | if( newKey >=0 && newKey <= 127) 46 | key = newKey; 47 | } 48 | 49 | void MidiNoteEvent::setVelocity(int newVelocity) 50 | { 51 | if( newVelocity >=0 && newVelocity <= 127) 52 | vel = newVelocity; 53 | } 54 | 55 | void MidiNoteEvent::setDetune(double newDetune) 56 | { 57 | detune = newDetune; 58 | } 59 | 60 | void MidiNoteEvent::setPriority(int newPriority) 61 | { 62 | if( newPriority >=0 ) 63 | priority = newPriority; 64 | } 65 | -------------------------------------------------------------------------------- /app/AboutWindow.cpp: -------------------------------------------------------------------------------- 1 | #include "AboutWindow.hpp" 2 | 3 | #include "../engine/core/Log.hpp" 4 | #include "../vendor/imgui/imgui.h" 5 | #include "App.hpp" 6 | 7 | constexpr char github[] = "https://github.com/RuiVarela/Senos"; 8 | 9 | namespace sns { 10 | 11 | AboutWindow::AboutWindow() 12 | { 13 | TAG = "AboutWindow"; 14 | m_window_name = "About"; 15 | } 16 | 17 | AboutWindow::~AboutWindow() { 18 | } 19 | 20 | 21 | void AboutWindow::render() { 22 | beforeRender(); 23 | 24 | ImGui::SetNextWindowSize(ImVec2(ImGui::GetFontSize() * 17.0f, 0.0f)); 25 | ImGui::Begin(m_window_name.c_str(), &m_showing, ImGuiWindowFlags_NoResize); 26 | 27 | { 28 | ImGui::TextDisabled("SENOS %s:", app()->versionName().c_str()); 29 | ImGui::Indent(); 30 | ImGui::TextWrapped("A sound exploration tool from a developer point of view."); 31 | ImGui::Spacing(); 32 | if (ImGui::Button(github)) 33 | platformShellOpen(github); 34 | 35 | ImGui::Unindent(); 36 | ImGui::Spacing(); 37 | 38 | ImGui::TextDisabled("MADE WITH:"); 39 | ImGui::BulletText("Sokol"); 40 | ImGui::BulletText("Dear ImGui"); 41 | ImGui::BulletText("ImGui Knobs"); 42 | ImGui::BulletText("tinyformat.h"); 43 | ImGui::BulletText("nlohmann/json"); 44 | ImGui::BulletText("libremidi"); 45 | ImGui::BulletText("microtar"); 46 | ImGui::BulletText("miniz-cpp"); 47 | 48 | ImGui::BulletText("TinySoundFont"); 49 | ImGui::BulletText("Open303"); 50 | ImGui::BulletText("dx7 music-synthesizer-for-android"); 51 | 52 | 53 | 54 | } 55 | 56 | aboutToFinishRender(); 57 | ImGui::End(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /vendor/libremidi/api.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace libremidi 5 | { 6 | //! MIDI API specifier arguments. 7 | enum class API 8 | { 9 | UNSPECIFIED, /*!< Search for a working compiled API. */ 10 | MACOSX_CORE, /*!< Macintosh OS-X Core Midi API. */ 11 | LINUX_ALSA, /*!< The Advanced Linux Sound Architecture API. */ 12 | LINUX_ALSA_SEQ = LINUX_ALSA, 13 | LINUX_ALSA_RAW, /*!< Raw ALSA API. */ 14 | UNIX_JACK, /*!< The JACK Low-Latency MIDI Server API. */ 15 | WINDOWS_MM, /*!< The Microsoft Multimedia MIDI API. */ 16 | WINDOWS_UWP, /*!< The Microsoft WinRT MIDI API. */ 17 | EMSCRIPTEN_WEBMIDI, /*!< Web MIDI API through Emscripten */ 18 | DUMMY /*!< A compilable but non-functional API. */ 19 | }; 20 | 21 | /** 22 | * \brief A static function to determine the available compiled MIDI APIs. 23 | 24 | The values returned in the std::vector can be compared against 25 | the enumerated list values. Note that there can be more than one 26 | API compiled for certain operating systems. 27 | */ 28 | std::vector available_apis() noexcept; 29 | 30 | /** 31 | * @brief Returns the default backend to use for the target OS. 32 | * 33 | * e.g. the one that uses the "recommended" system MIDI API. 34 | */ 35 | inline constexpr libremidi::API default_platform_api() noexcept 36 | { 37 | #if defined(__APPLE__) 38 | return API::MACOSX_CORE; 39 | #elif defined(_WIN32) 40 | return API::WINDOWS_MM; 41 | #elif defined(__linux__) 42 | return API::LINUX_ALSA_SEQ; 43 | #elif defined(__emscripten__) 44 | return API::EMSCRIPTEN_WEBMIDI; 45 | #else 46 | return API::DUMMY; 47 | #endif 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /engine/audio/CircularBuffer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../core/Lang.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace sns { 10 | 11 | template 12 | class CircularBuffer { 13 | public: 14 | using Type = T; 15 | 16 | CircularBuffer() 17 | :m_size(0), m_head(0), m_tail(0) { 18 | clear(); 19 | } 20 | 21 | void clear() { 22 | m_size = 0; 23 | m_head = 0; 24 | m_tail = 0; 25 | } 26 | 27 | void pop_front() { 28 | assert(!empty()); 29 | 30 | m_head = (m_head + 1) % capacity(); 31 | --m_size; 32 | } 33 | 34 | void push_back(Type v) { 35 | m_data[m_tail] = v; 36 | m_tail = (m_tail + 1) % capacity(); 37 | 38 | if (m_size == capacity()) { 39 | // We always accept data. when full and lose the front() 40 | m_head = (m_head + 1) % capacity(); 41 | } else { 42 | ++m_size; 43 | } 44 | } 45 | 46 | T &operator[](int index) { 47 | assert(index >= 0); 48 | assert(index < size()); 49 | return m_data[(m_head + index) % capacity()]; 50 | } 51 | 52 | T operator[](int index) const { 53 | assert(index >= 0); 54 | assert(index < size()); 55 | return m_data[(m_head + index) % capacity()]; 56 | } 57 | 58 | bool empty() const { return m_size == 0; } 59 | size_t size() const { return m_size; } 60 | size_t capacity() const { return m_data.size(); } 61 | 62 | Type front() { 63 | assert(!empty()); 64 | return m_data[m_head]; 65 | } 66 | private: 67 | std::array m_data; 68 | size_t m_size; 69 | size_t m_head; 70 | size_t m_tail; 71 | }; 72 | } // namespace sns -------------------------------------------------------------------------------- /vendor/imgui-knobs/imgui-knobs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "../imgui/imgui.h" 5 | 6 | typedef int ImGuiKnobFlags; 7 | 8 | enum ImGuiKnobFlags_ { 9 | ImGuiKnobFlags_NoTitle = 1 << 0, 10 | ImGuiKnobFlags_NoInput = 1 << 1, 11 | ImGuiKnobFlags_ValueTooltip = 1 << 2, 12 | ImGuiKnobFlags_DragHorizontal = 1 << 3, 13 | }; 14 | 15 | typedef int ImGuiKnobVariant; 16 | 17 | enum ImGuiKnobVariant_ { 18 | ImGuiKnobVariant_Tick = 1 << 0, 19 | ImGuiKnobVariant_Dot = 1 << 1, 20 | ImGuiKnobVariant_Wiper = 1 << 2, 21 | ImGuiKnobVariant_WiperOnly = 1 << 3, 22 | ImGuiKnobVariant_WiperDot = 1 << 4, 23 | ImGuiKnobVariant_Stepped = 1 << 5, 24 | ImGuiKnobVariant_Space = 1 << 6, 25 | }; 26 | 27 | namespace ImGuiKnobs { 28 | 29 | struct color_set { 30 | ImColor base; 31 | ImColor hovered; 32 | ImColor active; 33 | 34 | color_set(ImColor base, ImColor hovered, ImColor active) : base(base), hovered(hovered), active(active) {} 35 | 36 | color_set(ImColor color) { 37 | base = color; 38 | hovered = color; 39 | active = color; 40 | } 41 | }; 42 | 43 | bool Knob(const char *label, float *p_value, float v_min, float v_max, float speed = 0, const char *format = NULL, ImGuiKnobVariant variant = ImGuiKnobVariant_Tick, float size = 0, ImGuiKnobFlags flags = 0, int steps = 10); 44 | bool KnobInt(const char *label, int *p_value, int v_min, int v_max, float speed = 0, const char *format = NULL, ImGuiKnobVariant variant = ImGuiKnobVariant_Tick, float size = 0, ImGuiKnobFlags flags = 0, int steps = 10); 45 | }// namespace ImGuiKnobs 46 | -------------------------------------------------------------------------------- /app/ConfigurationMigrations.cpp: -------------------------------------------------------------------------------- 1 | #include "Configuration.hpp" 2 | 3 | #include "../engine/core/Log.hpp" 4 | #include "App.hpp" 5 | 6 | #include "../vendor/miniz/miniz.hpp" 7 | 8 | constexpr auto TAG = "Configuration"; 9 | 10 | //zip -d startup.zip "__MACOSX*" 11 | //zip -d startup.zip "*/.DS_Store" 12 | 13 | // std::vector buffer; 14 | // if (sns::readRawBinary("/Users/ruivarela/projects/Senos/app/assets/startup.zip", buffer)) { 15 | // sns::saveCppBinary(buffer.data(), buffer.size(), "/Users/ruivarela/projects/Senos/app/assets/startup.cpp"); 16 | // } 17 | const size_t startup_data_size = 20450; 18 | extern unsigned char startup_data[startup_data_size]; 19 | 20 | namespace sns { 21 | 22 | 23 | // 24 | // data migrations 25 | // 26 | void unpackAssetsData(Configuration cfg) { 27 | std::vector source(startup_data_size); 28 | memcpy(source.data(), startup_data, startup_data_size); 29 | 30 | zip_file zip(source); 31 | std::vector data; 32 | for (auto &member : zip.infolist()) { 33 | std::string filename(member.filename); 34 | if (member.file_size == 0) 35 | continue; 36 | 37 | zip.read(member, data); 38 | 39 | std::string destination = mergePaths(rootFolder(), filename); 40 | makeDirectoryForFile(destination); 41 | writeRawBinary(destination, data); 42 | Log::d(TAG, sfmt("Wrote File [%s] -> [%s]", filename, destination)); 43 | } 44 | } 45 | 46 | Configuration migrateApp(App* app, Configuration cfg, int from, int to) { 47 | if (app == nullptr) 48 | return cfg; 49 | 50 | if (from == 0) { 51 | unpackAssetsData(cfg); 52 | app->windowLayoutPlay(); 53 | } 54 | 55 | 56 | return cfg; 57 | } 58 | } -------------------------------------------------------------------------------- /vendor/libremidi/detail/semaphore.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | // Based on https://stackoverflow.com/a/27852868/1495627 6 | namespace libremidi 7 | { 8 | class semaphore 9 | { 10 | public: 11 | explicit semaphore(size_t count = 0) : count_{count} 12 | { 13 | } 14 | 15 | semaphore(const semaphore&) = delete; 16 | semaphore(semaphore&&) = delete; 17 | semaphore& operator=(const semaphore&) = delete; 18 | semaphore& operator=(semaphore&&) = delete; 19 | 20 | void notify() 21 | { 22 | std::lock_guard lock{mutex_}; 23 | ++count_; 24 | cv_.notify_one(); 25 | } 26 | 27 | void wait() 28 | { 29 | std::unique_lock lock{mutex_}; 30 | cv_.wait(lock, [&] { return count_ > 0; }); 31 | --count_; 32 | } 33 | 34 | bool try_wait() 35 | { 36 | std::lock_guard lock{mutex_}; 37 | 38 | if (count_ > 0) 39 | { 40 | --count_; 41 | return true; 42 | } 43 | 44 | return false; 45 | } 46 | 47 | template 48 | bool wait_for(const T& d) 49 | { 50 | std::unique_lock lock{mutex_}; 51 | auto finished = cv_.wait_for(lock, d, [&] { return count_ > 0; }); 52 | 53 | if (finished) 54 | --count_; 55 | 56 | return finished; 57 | } 58 | 59 | template 60 | bool wait_until(const T& t) 61 | { 62 | std::unique_lock lock{mutex_}; 63 | auto finished = cv_.wait_until(lock, t, [&] { return count_ > 0; }); 64 | 65 | if (finished) 66 | --count_; 67 | 68 | return finished; 69 | } 70 | 71 | private: 72 | std::mutex mutex_; 73 | std::condition_variable cv_; 74 | size_t count_; 75 | }; 76 | } 77 | -------------------------------------------------------------------------------- /engine/core/Worker.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Lang.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace sns { 10 | class WaitEvent { 11 | public: 12 | WaitEvent(bool set = false); 13 | 14 | WaitEvent(const WaitEvent& other) = delete; 15 | WaitEvent(WaitEvent&& other) = delete; 16 | WaitEvent& operator=(const WaitEvent& other) = delete; 17 | WaitEvent& operator=(WaitEvent&& other) = delete; 18 | 19 | // don't delete this without making sure thread have left the building 20 | ~WaitEvent() noexcept; 21 | 22 | void set(); 23 | bool waitAndReset(int max_period_ms = 1000); 24 | void cancel(); 25 | private: 26 | std::mutex m_mutex; 27 | std::condition_variable m_condition; 28 | bool m_set_state; 29 | bool m_canceled; 30 | }; 31 | 32 | 33 | class Worker { 34 | public: 35 | Worker(); 36 | 37 | Worker(const Worker& other) = delete; 38 | Worker(Worker&& other) = delete; 39 | Worker& operator=(const Worker& other) = delete; 40 | Worker& operator=(Worker&& other) = delete; 41 | 42 | virtual ~Worker(); 43 | 44 | void setSleepMs(int millis); 45 | 46 | void startWorking(); 47 | void stopWorking(); 48 | bool isWorking(); 49 | 50 | void signalWorkEnd(); 51 | void signalWorkArrived(); 52 | 53 | static void threadSleep(int const ms); 54 | static void threadSpin(); 55 | 56 | protected: 57 | // override these methods to add work on the thread 58 | virtual void workStep(); 59 | virtual void preWork(); 60 | virtual void postWork(); 61 | 62 | private: 63 | std::recursive_mutex m_runner_mutex; 64 | std::thread m_runner; 65 | WaitEvent m_runner_event; 66 | int m_sleep_milliseconds; 67 | bool m_working; 68 | 69 | virtual void pump(); 70 | }; 71 | 72 | 73 | } 74 | 75 | 76 | -------------------------------------------------------------------------------- /engine/instrument/dx7/fm_op_kernel.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* 3 | * Copyright 2012 Google Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | namespace dx7 { 18 | 19 | struct FmOpParams { 20 | int32_t level_in; // value to be computed (from level to gain[0]) 21 | int32_t gain_out; // computed value (gain[1] to gain[0]) 22 | int32_t freq; 23 | int32_t phase; 24 | }; 25 | 26 | class FmOpKernel { 27 | public: 28 | // gain1 and gain2 represent linear step: gain for sample i is 29 | // gain1 + (1 + i) / 64 * (gain2 - gain1) 30 | 31 | // This is the basic FM operator. No feedback. 32 | static void compute(int32_t *output, const int32_t *input, 33 | int32_t phase0, int32_t freq, 34 | int32_t gain1, int32_t gain2, bool add); 35 | 36 | // This is a sine generator, no feedback. 37 | static void compute_pure(int32_t *output, int32_t phase0, int32_t freq, 38 | int32_t gain1, int32_t gain2, bool add); 39 | 40 | // One op with feedback, no add. 41 | static void compute_fb(int32_t *output, int32_t phase0, int32_t freq, 42 | int32_t gain1, int32_t gain2, 43 | int32_t *fb_buf, int fb_gain, bool add); 44 | }; 45 | 46 | } -------------------------------------------------------------------------------- /engine/audio/RunningStats.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../core/Lang.hpp" 4 | 5 | namespace sns 6 | { 7 | class RunningAverage { 8 | public: 9 | explicit RunningAverage(int window, float initial_value = 0.0) 10 | :m_window(window) 11 | { 12 | reset(initial_value); 13 | } 14 | 15 | void reset(float initial_value) { 16 | m_average = 0.0f; 17 | m_sum = 0.0f; 18 | m_samples.clear(); 19 | add(initial_value); 20 | } 21 | 22 | float add(float value) { 23 | m_samples.push_back(value); 24 | m_sum += value; 25 | 26 | if (m_samples.size() > m_window) { 27 | m_sum -= m_samples.front(); 28 | m_samples.pop_front(); 29 | } 30 | 31 | m_average = m_sum / float(m_samples.size()); 32 | return m_average; 33 | } 34 | 35 | float average() const { return m_average; } 36 | private: 37 | int m_window; 38 | std::list m_samples; 39 | float m_sum; 40 | float m_average; 41 | }; 42 | 43 | 44 | class RunningRms { 45 | public: 46 | explicit RunningRms(int window, float initial_value = 0.0) 47 | :m_window(window) 48 | { 49 | reset(initial_value); 50 | } 51 | 52 | void reset(float initial_value) { 53 | m_rms = 0.0f; 54 | m_sum_of_squares = 0.0f; 55 | 56 | m_samples.clear(); 57 | add(initial_value); 58 | } 59 | 60 | float add(float value) { 61 | m_samples.push_back(value); 62 | m_sum_of_squares += value * value; 63 | 64 | if (m_samples.size() > m_window) { 65 | float front = m_samples.front(); 66 | m_samples.pop_front(); 67 | m_sum_of_squares -= front * front; 68 | } 69 | 70 | m_rms = std::sqrt(m_sum_of_squares / float(m_samples.size())); 71 | return m_rms; 72 | } 73 | 74 | float rms() const { return m_rms; } 75 | private: 76 | int m_window; 77 | std::list m_samples; 78 | float m_sum_of_squares; 79 | float m_rms; 80 | }; 81 | 82 | } -------------------------------------------------------------------------------- /engine/instrument/synthmachine/Oscillator.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../../audio/Audio.hpp" 4 | 5 | namespace sns { 6 | 7 | class Oscillator { 8 | public: 9 | 10 | enum class Kind { 11 | Off, 12 | Sine, 13 | Square, 14 | Triangle, 15 | Saw, 16 | Ramp, 17 | PolyblepSquare, 18 | PolyblepTriangle, 19 | PolyblepSaw, 20 | SmoothNoise, 21 | WhiteNoise, 22 | PinkNoise, 23 | 24 | Count 25 | }; 26 | 27 | Oscillator(float frequency = 1.0, Kind kind = Kind::Sine); 28 | 29 | std::string toString() const; 30 | 31 | static std::vector kindNames(); 32 | static bool hasDiscontinuities(Kind kind); 33 | 34 | void setKind(Kind kind); 35 | Kind kind() const; 36 | 37 | bool isOff() const; 38 | 39 | void setFrequency(float frequency); 40 | float frequency() const; 41 | 42 | bool endOfCycle() const; 43 | bool endOfRise() const; 44 | 45 | bool isRising() const; 46 | bool isFalling() const; 47 | 48 | float phase() const; 49 | 50 | uint64_t sampleCount() const; 51 | 52 | float next(); 53 | private: 54 | float m_phase; 55 | float m_phase_increment; 56 | bool m_end_of_cycle; 57 | bool m_end_of_rise; 58 | 59 | float m_frequency; 60 | Kind m_kind; 61 | 62 | float m_last_out; 63 | 64 | float m_last_keypoint; 65 | float m_last_interval; 66 | 67 | uint64_t m_sample_count; 68 | 69 | // pink 70 | float m_pink_k[7]; 71 | float m_pink_b[7]; 72 | float m_pink; 73 | void setupPinkNoise(); 74 | float whiteNoise(); 75 | }; 76 | 77 | std::string toString(Oscillator::Kind kind); 78 | } -------------------------------------------------------------------------------- /engine/audio/Easing.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../core/Lang.hpp" 4 | 5 | namespace sns { 6 | 7 | // 8 | // https://easings.net/ 9 | // 10 | enum class EasingKind { 11 | Linear, 12 | Sine, 13 | Quad, 14 | Cubic, 15 | Quart, 16 | Quint, 17 | Expo, 18 | 19 | Count 20 | }; 21 | 22 | enum class EasingMethod { 23 | Linear, 24 | InSine, OutSine, 25 | InQuad, OutQuad, 26 | InCubic, OutCubic, 27 | InQuart, OutQuart, 28 | InQuint, OutQuint, 29 | InExpo, OutExpo, 30 | 31 | Count 32 | }; 33 | 34 | 35 | template inline T easeInSine(T x) { return T(1.0 - cos((x * PI) / 2.0)); } 36 | template inline T easeOutSine(T x) { return T(sin((x * PI) / 2.0)); } 37 | 38 | template inline T easeInQuad(T x) { return x * x; } 39 | template inline T easeOutQuad(T x) { return T(1.0 - (1.0 - x) * (1.0 - x)); } 40 | 41 | template inline T easeInCubic(T x) { return x * x * x; } 42 | template inline T easeOutCubic(T x) { return T(1.0 - pow(1.0 - x, 3.0)); } 43 | 44 | template inline T easeInQuart(T x) { return x * x * x * x; } 45 | template inline T easeOutQuart(T x) { return T(1.0 - pow(1.0 - x, 4.0)); } 46 | 47 | template inline T easeInQuint(T x) { return x * x * x * x * x; } 48 | template inline T easeOutQuint(T x) { return T(1.0 - pow(1.0 - x, 5.0)); } 49 | 50 | template inline T easeInExpo(T x) { return T(x > 0.0 ? pow(2.0, 10.0 * x - 10.0) : 0.0); } 51 | template inline T easeOutExpo(T x) { return T(x < 1.0 ? 1.0 - pow(2.0, -10.0 * x) : 1.0); } 52 | 53 | std::pair easingPair(EasingKind kind); 54 | 55 | std::string toString(EasingKind kind); 56 | std::string toString(EasingMethod method); 57 | 58 | std::vector easingNames(); 59 | std::vector easingKindNames(); 60 | 61 | float easing(EasingMethod method, float x); 62 | 63 | } -------------------------------------------------------------------------------- /engine/instrument/dx7/freqlut.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Resolve frequency signal (1.0 in Q24 format = 1 octave) to phase delta. 18 | // The LUT is just a global, and we'll need the init function to be called before use. 19 | 20 | #include "synth.h" 21 | #include "freqlut.h" 22 | 23 | namespace dx7 { 24 | 25 | #define LG_N_SAMPLES 10 26 | #define N_SAMPLES (1 << LG_N_SAMPLES) 27 | #define SAMPLE_SHIFT (24 - LG_N_SAMPLES) 28 | 29 | #define MAX_LOGFREQ_INT 20 30 | 31 | int32_t lut[N_SAMPLES + 1]; 32 | 33 | void Freqlut::init(double sample_rate) { 34 | double y = (1LL << (24 + MAX_LOGFREQ_INT)) / sample_rate; 35 | double inc = pow(2, 1.0 / N_SAMPLES); 36 | for (int i = 0; i < N_SAMPLES + 1; i++) { 37 | lut[i] = (int32_t)floor(y + 0.5); 38 | y *= inc; 39 | } 40 | } 41 | 42 | // Note: if logfreq is more than 20.0, the results will be inaccurate. However, 43 | // that will be many times the Nyquist rate. 44 | int32_t Freqlut::lookup(int32_t logfreq) { 45 | int ix = (logfreq & 0xffffff) >> SAMPLE_SHIFT; 46 | 47 | int32_t y0 = lut[ix]; 48 | int32_t y1 = lut[ix + 1]; 49 | int lowbits = logfreq & ((1 << SAMPLE_SHIFT) - 1); 50 | int32_t y = y0 + ((((int64_t)(y1 - y0) * (int64_t)lowbits)) >> SAMPLE_SHIFT); 51 | int hibits = logfreq >> 24; 52 | return y >> (MAX_LOGFREQ_INT - hibits); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /vendor/libremidi/writer.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Dimitri Diakopoulos All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | #pragma once 27 | #include 28 | #include 29 | #include 30 | 31 | namespace libremidi 32 | { 33 | struct writer 34 | { 35 | public: 36 | int ticksPerQuarterNote{120}; 37 | std::vector tracks; 38 | 39 | void add_event(int tick, int track, message m); 40 | void add_event(int track, track_event m); 41 | 42 | void add_track(); 43 | 44 | void write(std::ostream& out); 45 | }; 46 | } 47 | 48 | #if defined(LIBREMIDI_HEADER_ONLY) 49 | # include 50 | #endif 51 | -------------------------------------------------------------------------------- /engine/core/Log.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Lang.hpp" 3 | #include "Text.hpp" 4 | #include "Worker.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace sns 12 | { 13 | enum class LogLevel { 14 | Debug, 15 | Info, 16 | Warning, 17 | Error 18 | }; 19 | 20 | 21 | class Log : public Worker { 22 | public: 23 | using Handler = std::function; 24 | 25 | static void d(std::string const& tag, std::string const& message) { logger().log(LogLevel::Debug, tag, message); } 26 | static void i(std::string const& tag, std::string const& message) { logger().log(LogLevel::Debug, tag, message); } 27 | static void w(std::string const& tag, std::string const& message) { logger().log(LogLevel::Debug, tag, message); } 28 | static void e(std::string const& tag, std::string const& message) { logger().log(LogLevel::Debug, tag, message); } 29 | 30 | static Log& logger(); 31 | 32 | void log(LogLevel level, std::string const& tag, std::string const& message); 33 | 34 | void setWindowHandler(Handler handler); 35 | void setFilename(std::string filename); 36 | 37 | void setLeveL(LogLevel level); 38 | LogLevel level() const; 39 | 40 | protected: 41 | void workStep() override; 42 | void preWork() override; 43 | void postWork() override; 44 | 45 | private: 46 | Log(); 47 | ~Log() override; 48 | 49 | LogLevel m_level; 50 | Handler m_window_handler; 51 | std::string m_filename; 52 | uint64_t m_max_file_size; 53 | uint64_t m_message_count; 54 | 55 | 56 | struct Message { 57 | LogLevel level; 58 | std::string tag; 59 | std::string message; 60 | std::chrono::time_point ts; 61 | }; 62 | std::list m_messages; 63 | std::mutex m_messages_mutex; 64 | 65 | void flush(int flush_size); 66 | void sendMessage(Message const& message); 67 | void addFirstMessage(); 68 | void addLastMessage(); 69 | }; 70 | 71 | } -------------------------------------------------------------------------------- /engine/instrument/dx7/sin.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* 3 | * Copyright 2012 Google Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | namespace dx7 { 18 | 19 | class Sin { 20 | public: 21 | Sin(); 22 | 23 | static void init(); 24 | static int32_t lookup(int32_t phase); 25 | static int32_t compute(int32_t phase); 26 | 27 | // A more accurate sine, both input and output Q30 28 | static int32_t compute10(int32_t phase); 29 | }; 30 | 31 | #define SIN_LG_N_SAMPLES 10 32 | #define SIN_N_SAMPLES (1 << SIN_LG_N_SAMPLES) 33 | 34 | #define SIN_INLINE 35 | 36 | // Use twice as much RAM for the LUT but avoid a little computation 37 | #define SIN_DELTA 38 | 39 | #ifdef SIN_DELTA 40 | extern int32_t sintab[SIN_N_SAMPLES << 1]; 41 | #else 42 | extern int32_t sintab[SIN_N_SAMPLES + 1]; 43 | #endif 44 | 45 | #ifdef SIN_INLINE 46 | inline 47 | int32_t Sin::lookup(int32_t phase) { 48 | const int SHIFT = 24 - SIN_LG_N_SAMPLES; 49 | int lowbits = phase & ((1 << SHIFT) - 1); 50 | #ifdef SIN_DELTA 51 | int phase_int = (phase >> (SHIFT - 1)) & ((SIN_N_SAMPLES - 1) << 1); 52 | int dy = sintab[phase_int]; 53 | int y0 = sintab[phase_int + 1]; 54 | 55 | return y0 + (((int64_t)dy * (int64_t)lowbits) >> SHIFT); 56 | #else 57 | int phase_int = (phase >> SHIFT) & (SIN_N_SAMPLES - 1); 58 | int y0 = sintab[phase_int]; 59 | int y1 = sintab[phase_int + 1]; 60 | 61 | return y0 + (((int64_t)(y1 - y0) * (int64_t)lowbits) >> SHIFT); 62 | #endif 63 | } 64 | #endif 65 | 66 | } -------------------------------------------------------------------------------- /engine/instrument/tb303/rosic_DecayEnvelope.cpp: -------------------------------------------------------------------------------- 1 | #include "rosic_DecayEnvelope.h" 2 | using namespace rosic; 3 | 4 | //------------------------------------------------------------------------------------------------- 5 | // construction/destruction: 6 | 7 | DecayEnvelope::DecayEnvelope() 8 | { 9 | c = 1.0; 10 | y = 1.0; 11 | yInit = 1.0; 12 | tau = 200.0; 13 | fs = 44100.0; 14 | normalizeSum = false; 15 | calculateCoefficient(); 16 | } 17 | 18 | DecayEnvelope::~DecayEnvelope() 19 | { 20 | 21 | } 22 | 23 | //------------------------------------------------------------------------------------------------- 24 | // parameter settings: 25 | 26 | void DecayEnvelope::setSampleRate(double newSampleRate) 27 | { 28 | if( newSampleRate > 0.0 ) 29 | { 30 | fs = newSampleRate; 31 | calculateCoefficient(); 32 | } 33 | } 34 | 35 | void DecayEnvelope::setDecayTimeConstant(double newTimeConstant) 36 | { 37 | if( newTimeConstant > 0.001 ) // at least 0.001 ms decay 38 | { 39 | tau = newTimeConstant; 40 | calculateCoefficient(); 41 | } 42 | } 43 | 44 | void DecayEnvelope::setNormalizeSum(bool shouldNormalizeSum) 45 | { 46 | normalizeSum = shouldNormalizeSum; 47 | calculateCoefficient(); 48 | } 49 | 50 | //------------------------------------------------------------------------------------------------- 51 | // others: 52 | 53 | void DecayEnvelope::trigger() 54 | { 55 | y = yInit; 56 | } 57 | 58 | bool DecayEnvelope::endIsReached(double threshold) 59 | { 60 | if( y < threshold ) 61 | return true; 62 | else 63 | return false; 64 | } 65 | 66 | //------------------------------------------------------------------------------------------------- 67 | // internal functions: 68 | 69 | void DecayEnvelope::calculateCoefficient() 70 | { 71 | c = exp( -1.0 / (0.001*tau*fs) ); 72 | if( normalizeSum == true ) 73 | yInit = (1.0-c)/c; 74 | else 75 | yInit = 1.0/c; 76 | } 77 | 78 | -------------------------------------------------------------------------------- /engine/audio/Recorder.cpp: -------------------------------------------------------------------------------- 1 | #include "Recorder.hpp" 2 | #include "../core/Log.hpp" 3 | 4 | namespace sns { 5 | 6 | Recorder::Recorder() 7 | :TAG("Recorder"), 8 | m_accepting(false), 9 | m_flush_size(1024 * 10), 10 | m_received_samples(0) 11 | { 12 | 13 | setSleepMs(500); 14 | } 15 | 16 | Recorder::~Recorder() { 17 | 18 | } 19 | 20 | void Recorder::startRecording(std::string const& filename) { 21 | stopRecording(); 22 | m_received_samples = 0; 23 | m_filename = filename; 24 | startWorking(); 25 | } 26 | 27 | void Recorder::stopRecording() { 28 | if (isWorking()) 29 | stopWorking(); 30 | } 31 | 32 | bool Recorder::isRecording() { 33 | return isWorking(); 34 | } 35 | 36 | uint64_t Recorder::recordedMilliseconds() { 37 | return audioMilliseconds(m_received_samples); 38 | } 39 | 40 | bool Recorder::isAccepting() { 41 | return isWorking() && m_accepting; 42 | } 43 | 44 | void Recorder::push(float sample) { 45 | if (m_accepting) { 46 | std::unique_lock lock(m_mutex); 47 | 48 | m_write.push_back(sample); 49 | m_received_samples += 1; 50 | 51 | if (m_write.size() > m_flush_size) 52 | signalWorkArrived(); 53 | } 54 | } 55 | 56 | void Recorder::workStep() { 57 | writeToFile(); 58 | } 59 | 60 | void Recorder::preWork() { 61 | sns::makeDirectoryForFile(m_filename); 62 | 63 | Log::i(TAG, sfmt("Starting recorder (%s)...", m_filename)); 64 | m_accepting = m_output.open(m_filename); 65 | 66 | if (!m_accepting) { 67 | Log::e(TAG, sfmt("Failed to start output to (%s)", m_filename)); 68 | } 69 | } 70 | 71 | void Recorder::postWork() { 72 | m_accepting = false; 73 | 74 | writeToFile(); 75 | m_output.close(); 76 | 77 | Log::i(TAG, "Stopped recorder..."); 78 | } 79 | 80 | void Recorder::writeToFile() { 81 | m_read.clear(); 82 | 83 | { 84 | std::unique_lock lock(m_mutex); 85 | std::swap(m_write, m_read); 86 | } 87 | 88 | if (!m_read.empty()) { 89 | //Log::e(TAG, sfmt("About to save %d samples", m_read.size())); 90 | m_output.write(m_read.data(), (int)m_read.size()); 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /engine/instrument/SynthMachine.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Instrument.hpp" 4 | 5 | #include "synthmachine/Oscillator.hpp" 6 | #include "synthmachine/Envelope.hpp" 7 | #include "synthmachine/Value.hpp" 8 | #include "synthmachine/Filter.hpp" 9 | 10 | namespace sns { 11 | constexpr int SynthMachineOscCount = 3; 12 | 13 | struct SynthMachineEmitter { 14 | uint64_t id = 0; 15 | int note = 0; 16 | float note_frequency = 0.0f; 17 | bool on = false; 18 | bool killed = false; 19 | uint64_t produced = 0; 20 | 21 | Oscillator osc[SynthMachineOscCount]; 22 | Oscillator lfo[SynthMachineOscCount]; 23 | Envelope env; 24 | }; 25 | 26 | class SynthMachine : public BaseInstrument { 27 | public: 28 | SynthMachine(); 29 | ~SynthMachine() override; 30 | 31 | void setValues(ParametersValues const& values) override; 32 | 33 | float next() override; 34 | 35 | void onMidi(MidiMessage const& message) override; 36 | void setNote(int note, float velocity) override; 37 | 38 | static ParametersValues defaultParameters(); 39 | 40 | void panic() override; 41 | private: 42 | std::vector m_emiters; 43 | void prune(); 44 | 45 | bool m_do_log; 46 | 47 | uint64_t m_emitter_counter; 48 | Value m_osc_volume[SynthMachineOscCount]; 49 | Value m_osc_detune[SynthMachineOscCount]; 50 | float m_lfo_pitch[SynthMachineOscCount]; 51 | Value m_lfo_amp[SynthMachineOscCount]; 52 | 53 | float m_last_note_frequency; 54 | 55 | Filter m_filter; 56 | float m_filter_cutoff_dial; 57 | Value m_filter_cutoff; 58 | Value m_filter_resonance; 59 | Value m_filter_drive; 60 | 61 | bool m_filter_keyboard_tracking; 62 | 63 | void updateFilterCutoff(); 64 | 65 | bool m_mono; 66 | Value m_portamento; 67 | uint64_t m_portamento_time; 68 | 69 | Value m_pitch_bend; 70 | Value m_volume; 71 | 72 | void setupEmitter(SynthMachineEmitter& emitter, int note); 73 | void updateEmittersParameter(Parameter parameter, float value, bool setup = false, SynthMachineEmitter* filter = nullptr); 74 | 75 | 76 | void releaseNote(SynthMachineEmitter& emitter); 77 | }; 78 | 79 | } -------------------------------------------------------------------------------- /engine/instrument/dx7/patch.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #include "synth.h" 17 | #include "patch.h" 18 | 19 | namespace dx7 { 20 | 21 | void UnpackPatch(const char bulk[128], char patch[156]) { 22 | for (int op = 0; op < 6; op++) { 23 | // eg rate and level, brk pt, depth, scaling 24 | memcpy(patch + op * 21, bulk + op * 17, 11); 25 | char leftrightcurves = bulk[op * 17 + 11]; 26 | patch[op * 21 + 11] = leftrightcurves & 3; 27 | patch[op * 21 + 12] = (leftrightcurves >> 2) & 3; 28 | char detune_rs = bulk[op * 17 + 12]; 29 | patch[op * 21 + 13] = detune_rs & 7; 30 | patch[op * 21 + 20] = detune_rs >> 3; 31 | char kvs_ams = bulk[op * 17 + 13]; 32 | patch[op * 21 + 14] = kvs_ams & 3; 33 | patch[op * 21 + 15] = kvs_ams >> 2; 34 | patch[op * 21 + 16] = bulk[op * 17 + 14]; // output level 35 | char fcoarse_mode = bulk[op * 17 + 15]; 36 | patch[op * 21 + 17] = fcoarse_mode & 1; 37 | patch[op * 21 + 18] = fcoarse_mode >> 1; 38 | patch[op * 21 + 19] = bulk[op * 17 + 16]; // fine freq 39 | } 40 | memcpy(patch + 126, bulk + 102, 9); // pitch env, algo 41 | char oks_fb = bulk[111]; 42 | patch[135] = oks_fb & 7; 43 | patch[136] = oks_fb >> 3; 44 | memcpy(patch + 137, bulk + 112, 4); // lfo 45 | char lpms_lfw_lks = bulk[116]; 46 | patch[141] = lpms_lfw_lks & 1; 47 | patch[142] = (lpms_lfw_lks >> 1) & 7; 48 | patch[143] = lpms_lfw_lks >> 4; 49 | memcpy(patch + 144, bulk + 117, 11); // transpose, name 50 | patch[155] = 0x3f; // operator on/off 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /app/instrument/TB303Window.cpp: -------------------------------------------------------------------------------- 1 | #include "TB303Window.hpp" 2 | 3 | #include "../App.hpp" 4 | #include "../vendor/imgui/imgui.h" 5 | #include "../vendor/imgui-knobs/imgui-knobs.h" 6 | #include "../../engine/core/Text.hpp" 7 | #include "../../engine/core/Log.hpp" 8 | #include "../../engine/instrument/TB303.hpp" 9 | 10 | 11 | namespace sns { 12 | 13 | TB303Window::TB303Window() { 14 | TAG = "TB303Window"; 15 | m_window_name = "TB-303"; 16 | m_subtype = InstrumentIdTB303; 17 | m_is_instrument = true; 18 | m_has_presets = true; 19 | } 20 | 21 | TB303Window::~TB303Window() { 22 | 23 | } 24 | 25 | void TB303Window::render() { 26 | beforeRender(); 27 | ImGui::Begin(m_window_name.c_str(), &m_showing, ImGuiWindowFlags_NoResize); 28 | 29 | float presets_height = ImGui::GetFontSize() * 9.5f; 30 | 31 | { 32 | float width = ImGui::GetFontSize() * 24.3f; 33 | 34 | ImGui::BeginChild("bass_line_block", ImVec2(width, presets_height), true); 35 | renderBassLine(); 36 | ImGui::EndChild(); 37 | } 38 | 39 | 40 | 41 | ImGui::SameLine(); 42 | renderPresets(presets_height); 43 | 44 | 45 | aboutToFinishRender(); 46 | ImGui::End(); 47 | } 48 | 49 | void TB303Window::renderBassLine() { 50 | ImGui::TextDisabled("Bass Line"); 51 | 52 | pKnob("Tuning", ParameterTuning); 53 | ImGui::SameLine(); 54 | pKnob("Cutoff", ParameterFilterBase + ParameterFilterCutoff); 55 | ImGui::SameLine(); 56 | pKnob("Res.", ParameterFilterBase + ParameterFilterResonance); 57 | ImGui::SameLine(); 58 | pKnob("Env M.", ParameterEnvBase + ParameterEnvMod); 59 | ImGui::SameLine(); 60 | pKnob("Decay", ParameterEnvBase + ParameterEnvDecay); 61 | ImGui::SameLine(); 62 | pKnob("Accent", ParameterAccent); 63 | ImGui::SameLine(); 64 | pKnob("Volume", ParameterVolume); 65 | 66 | 67 | int osc_kind = int(m_values[ParameterOscBase + ParameterOscKind]); 68 | bool changed = false; 69 | changed |= ImGui::RadioButton("Saw", &osc_kind, 0); 70 | ImGui::SameLine(); 71 | changed |= ImGui::RadioButton("Square", &osc_kind, 1); 72 | if (changed) { 73 | m_values[ParameterOscBase + ParameterOscKind] = float(osc_kind); 74 | applyInstrumentValues(); 75 | } 76 | } 77 | 78 | } -------------------------------------------------------------------------------- /engine/instrument/dx7/exp2.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #include "synth.h" 17 | #include "exp2.h" 18 | 19 | namespace dx7 { 20 | 21 | int32_t exp2tab[EXP2_N_SAMPLES << 1]; 22 | 23 | void Exp2::init() { 24 | double inc = exp2(1.0 / EXP2_N_SAMPLES); 25 | double y = 1 << 30; 26 | for (int i = 0; i < EXP2_N_SAMPLES; i++) { 27 | exp2tab[(i << 1) + 1] = (int32_t)floor(y + 0.5); 28 | y *= inc; 29 | } 30 | for (int i = 0; i < EXP2_N_SAMPLES - 1; i++) { 31 | exp2tab[i << 1] = exp2tab[(i << 1) + 3] - exp2tab[(i << 1) + 1]; 32 | } 33 | exp2tab[(EXP2_N_SAMPLES << 1) - 2] = (1U << 31) - exp2tab[(EXP2_N_SAMPLES << 1) - 1]; 34 | } 35 | 36 | int32_t tanhtab[TANH_N_SAMPLES << 1]; 37 | 38 | static double dtanh(double y) { 39 | return 1 - y * y; 40 | } 41 | 42 | void Tanh::init() { 43 | double step = 4.0 / TANH_N_SAMPLES; 44 | double y = 0; 45 | for (int i = 0; i < TANH_N_SAMPLES; i++) { 46 | tanhtab[(i << 1) + 1] = (1 << 24) * y + 0.5; 47 | //printf("%d\n", tanhtab[(i << 1) + 1]); 48 | // Use a basic 4th order Runge-Kutte to compute tanh from its 49 | // differential equation. 50 | double k1 = dtanh(y); 51 | double k2 = dtanh(y + 0.5 * step * k1); 52 | double k3 = dtanh(y + 0.5 * step * k2); 53 | double k4 = dtanh(y + step * k3); 54 | double dy = (step / 6) * (k1 + k4 + 2 * (k2 + k3)); 55 | y += dy; 56 | } 57 | for (int i = 0; i < TANH_N_SAMPLES - 1; i++) { 58 | tanhtab[i << 1] = tanhtab[(i << 1) + 3] - tanhtab[(i << 1) + 1]; 59 | } 60 | int32_t lasty = (1 << 24) * y + 0.5; 61 | tanhtab[(TANH_N_SAMPLES << 1) - 2] = lasty - tanhtab[(TANH_N_SAMPLES << 1) - 1]; 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /vendor/microtar/microtar.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017 rxi 3 | * 4 | * This library is free software; you can redistribute it and/or modify it 5 | * under the terms of the MIT license. See `microtar.c` for details. 6 | */ 7 | 8 | #ifndef MICROTAR_H 9 | #define MICROTAR_H 10 | 11 | #include 12 | #include 13 | 14 | #define MTAR_VERSION "0.1.0" 15 | 16 | enum { 17 | MTAR_ESUCCESS = 0, 18 | MTAR_EFAILURE = -1, 19 | MTAR_EOPENFAIL = -2, 20 | MTAR_EREADFAIL = -3, 21 | MTAR_EWRITEFAIL = -4, 22 | MTAR_ESEEKFAIL = -5, 23 | MTAR_EBADCHKSUM = -6, 24 | MTAR_ENULLRECORD = -7, 25 | MTAR_ENOTFOUND = -8 26 | }; 27 | 28 | enum { 29 | MTAR_TREG = '0', 30 | MTAR_TLNK = '1', 31 | MTAR_TSYM = '2', 32 | MTAR_TCHR = '3', 33 | MTAR_TBLK = '4', 34 | MTAR_TDIR = '5', 35 | MTAR_TFIFO = '6' 36 | }; 37 | 38 | typedef struct { 39 | unsigned mode; 40 | unsigned owner; 41 | unsigned size; 42 | unsigned mtime; 43 | unsigned type; 44 | char name[100]; 45 | char linkname[100]; 46 | } mtar_header_t; 47 | 48 | 49 | typedef struct mtar_t mtar_t; 50 | 51 | struct mtar_t { 52 | int (*read)(mtar_t *tar, void *data, unsigned size); 53 | int (*write)(mtar_t *tar, const void *data, unsigned size); 54 | int (*seek)(mtar_t *tar, unsigned pos); 55 | int (*close)(mtar_t *tar); 56 | void *stream; 57 | unsigned pos; 58 | unsigned remaining_data; 59 | unsigned last_header; 60 | }; 61 | 62 | 63 | const char* mtar_strerror(int err); 64 | 65 | int mtar_open(mtar_t *tar, const char *filename, const char *mode); 66 | int mtar_close(mtar_t *tar); 67 | 68 | int mtar_seek(mtar_t *tar, unsigned pos); 69 | int mtar_rewind(mtar_t *tar); 70 | int mtar_next(mtar_t *tar); 71 | int mtar_find(mtar_t *tar, const char *name, mtar_header_t *h); 72 | int mtar_read_header(mtar_t *tar, mtar_header_t *h); 73 | int mtar_read_data(mtar_t *tar, void *ptr, unsigned size); 74 | 75 | int mtar_write_header(mtar_t *tar, const mtar_header_t *h); 76 | int mtar_write_file_header(mtar_t *tar, const char *name, unsigned size); 77 | int mtar_write_dir_header(mtar_t *tar, const char *name); 78 | int mtar_write_data(mtar_t *tar, const void *data, unsigned size); 79 | int mtar_finalize(mtar_t *tar); 80 | 81 | 82 | #endif 83 | -------------------------------------------------------------------------------- /engine/Engine.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core/Worker.hpp" 4 | #include "audio/RunningStats.hpp" 5 | #include "audio/Recorder.hpp" 6 | #include "audio/Midi.hpp" 7 | #include "audio/Analyser.hpp" 8 | #include "instrument/Instrument.hpp" 9 | #include "Sequencer.hpp" 10 | #include "Chainer.hpp" 11 | 12 | namespace sns { 13 | 14 | class FeedbackCallback { 15 | public: 16 | FeedbackCallback() {} 17 | virtual ~FeedbackCallback() {} 18 | virtual void onInstrumentParamsFeedback(InstrumentId instrument_id, ParametersValues const& values) {} 19 | virtual void onMidiNotesFeedback(InstrumentId instrument_id, std::set const& notes_pressed) {} 20 | 21 | virtual void onSequencerState(Sequencer::State state) {} 22 | virtual void onChainerState(Chainer::State state) {} 23 | }; 24 | 25 | class Engine { 26 | public: 27 | Engine(); 28 | ~Engine(); 29 | 30 | void setFeedbackCallback(FeedbackCallback* callback); 31 | 32 | Recorder& recorder(); 33 | Midi& midi(); 34 | Analyser& analyser(); 35 | Sequencer& sequencer(); 36 | Chainer& chainer(); 37 | 38 | void fill(float* buffer, int num_frames, int num_channels); 39 | void stats(float& synthesis_average_ms, uint64_t& produced_ms, int& last_fill_samples); 40 | 41 | void setInstrumentParams(InstrumentId instrument_id, ParametersValues const& values); 42 | void setInstrumentNote(InstrumentId instrument_id, int note, float velocity); 43 | void setSequencerConfiguration(Sequencer::Configuration const& configuration); 44 | void setChainerConfiguration(Chainer::Configuration const& configuration); 45 | 46 | void produceSamples(uint64_t count, float* buffer); 47 | 48 | void panic(); 49 | private: 50 | std::string TAG; 51 | std::recursive_mutex m_sync_mutex; 52 | int m_last_fill_samples; // last filled samples 53 | uint64_t m_produced_samples_counter; // total produced samples 54 | RunningAverage m_synthesis_duration_ms; // average time we take to produce a sample 55 | 56 | std::array, InstrumentCount> m_instruments; 57 | Recorder m_recorder; 58 | Midi m_midi; 59 | Sequencer m_sequencer; 60 | Chainer m_chainer; 61 | Analyser m_analyser; 62 | 63 | std::list> m_actions; 64 | 65 | FeedbackCallback* m_feedback_callback; 66 | }; 67 | 68 | } -------------------------------------------------------------------------------- /engine/audio/Audio.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../core/Lang.hpp" 4 | #include "Easing.hpp" 5 | 6 | namespace sns { 7 | 8 | // 9 | // we use a predefine audio configuration 10 | // Mono, 44100, float (32bit) [-1.0f, 1.0f] 11 | // 12 | constexpr uint64_t SampleRate = 44100; // audio sample rate 13 | 14 | constexpr uint64_t Microseconds = 1000000; // microseconds in a seconds 15 | constexpr uint64_t Milliseconds = 1000; // milliseconds in a seconds 16 | 17 | constexpr float DefaultPressVelocity = 0.65f; 18 | constexpr float AccentPressVelocity = 1.0f; 19 | 20 | constexpr uint64_t audioMicroseconds(uint64_t samples) { return (samples * Microseconds) / SampleRate; } 21 | constexpr uint64_t audioMilliseconds(uint64_t samples) { return (samples * Milliseconds) / SampleRate; } 22 | constexpr uint64_t samplesFromMicroseconds(uint64_t microseconds) { return (microseconds * SampleRate) / Microseconds; } 23 | constexpr uint64_t samplesFromMilliseconds(uint64_t milliseconds) { return (milliseconds * SampleRate) / Milliseconds; } 24 | 25 | std::string timecode(uint64_t milliseconds); 26 | 27 | 28 | float toDb(float linear); 29 | float fromDb(float db); 30 | 31 | float toCent(float factor); 32 | float fromCent(float cent); 33 | 34 | float toSemitone(float factor); 35 | float fromSemitone(float semitone); 36 | 37 | 38 | float linearToLinear(float in, float in_min, float in_max, float out_min, float out_max); 39 | float linearToExponential(float in, float in_min, float in_max, float out_min, float out_max); 40 | 41 | 42 | 43 | // 44 | // Octaves and Notes 45 | // 46 | static constexpr int TotalOctaves = 11; 47 | static constexpr int TotalNotes = TotalOctaves * 12; 48 | 49 | std::string noteName(int note, bool octave = false, bool alternate = false); 50 | int noteIndex(int note, int octave); // note [0 <-> 11], octave [-1 <-> TotalOctaves - 2 ] 51 | int noteOctave(int note); // note [0 <-> TotalOctaves * 12] 52 | 53 | 54 | std::array noteFrequencies(); 55 | float noteFrequency(int note); 56 | 57 | // 58 | // Audio Helper 59 | // 60 | float SoftLimit(float x); // Soft Limiting function ported extracted from pichenettes/stmlib 61 | float SoftClip(float x); // Soft Clipping function extracted from pichenettes/stmlib 62 | float HardClip(float x); 63 | } -------------------------------------------------------------------------------- /engine/instrument/tb303/rosic_Complex.cpp: -------------------------------------------------------------------------------- 1 | #include "rosic_Complex.h" 2 | using namespace rosic; 3 | 4 | //------------------------------------------------------------------------------------------------- 5 | // construction/destruction: 6 | 7 | Complex::Complex() 8 | { 9 | re = im = 0.0; 10 | } 11 | 12 | Complex::Complex(double reInit) 13 | { 14 | re = reInit; 15 | im = 0.0; 16 | } 17 | 18 | Complex::Complex(double reInit, double imInit) 19 | { 20 | re = reInit; 21 | im = imInit; 22 | } 23 | 24 | Complex::~Complex() 25 | { 26 | 27 | } 28 | 29 | //------------------------------------------------------------------------------------------------- 30 | // magnitude, angle, etc. 31 | 32 | double Complex::getRadius() 33 | { 34 | return sqrt(re*re + im*im); 35 | } 36 | 37 | double Complex::getAngle() 38 | { 39 | if((re==0.0) && (im==0)) 40 | return 0.0; 41 | else 42 | return atan2(im, re); 43 | } 44 | 45 | void Complex::setRadius(double newRadius) 46 | { 47 | double phi = getAngle(); 48 | sinCos(phi, &im, &re); 49 | re *= newRadius; // re = newRadius * cos(phi); 50 | im *= newRadius; // im = newRadius * sin(phi); 51 | } 52 | 53 | void Complex::setAngle(double newAngle) 54 | { 55 | double r = getRadius(); 56 | sinCos(newAngle, &im, &re); 57 | re *= r; // re = r * cos(newAngle); 58 | im *= r; // im = r * sin(newAngle); 59 | } 60 | 61 | void Complex::setRadiusAndAngle(double newRadius, double newAngle) 62 | { 63 | sinCos(newAngle, &im, &re); 64 | re *= newRadius; // re = newRadius * cos(newAngle); 65 | im *= newRadius; // im = newRadius * sin(newAngle); 66 | } 67 | 68 | Complex Complex::getConjugate() 69 | { 70 | return Complex(re, -im); 71 | } 72 | 73 | Complex Complex::getReciprocal() 74 | { 75 | double scaler = 1.0 / (re*re + im*im); 76 | return Complex(scaler*re, -scaler*im); 77 | } 78 | 79 | bool Complex::isReal() 80 | { 81 | return (im == 0.0); 82 | } 83 | 84 | bool Complex::isImaginary() 85 | { 86 | return (re == 0.0); 87 | } 88 | 89 | bool Complex::isInfinite() 90 | { 91 | if( re == INF || re == NEG_INF || im == INF || im == NEG_INF ) 92 | return true; 93 | else 94 | return false; 95 | } 96 | -------------------------------------------------------------------------------- /engine/instrument/dx7/exp2.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* 3 | * Copyright 2012 Google Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | namespace dx7 { 19 | 20 | class Exp2 { 21 | public: 22 | Exp2(); 23 | 24 | static void init(); 25 | 26 | // Q24 in, Q24 out 27 | static int32_t lookup(int32_t x); 28 | }; 29 | 30 | #define EXP2_LG_N_SAMPLES 10 31 | #define EXP2_N_SAMPLES (1 << EXP2_LG_N_SAMPLES) 32 | 33 | #define EXP2_INLINE 34 | 35 | extern int32_t exp2tab[EXP2_N_SAMPLES << 1]; 36 | 37 | #ifdef EXP2_INLINE 38 | inline 39 | int32_t Exp2::lookup(int32_t x) { 40 | const int SHIFT = 24 - EXP2_LG_N_SAMPLES; 41 | int lowbits = x & ((1 << SHIFT) - 1); 42 | int x_int = (x >> (SHIFT - 1)) & ((EXP2_N_SAMPLES - 1) << 1); 43 | int dy = exp2tab[x_int]; 44 | int y0 = exp2tab[x_int + 1]; 45 | 46 | int y = y0 + (((int64_t)dy * (int64_t)lowbits) >> SHIFT); 47 | return y >> (6 - (x >> 24)); 48 | } 49 | #endif 50 | 51 | class Tanh { 52 | public: 53 | static void init(); 54 | 55 | // Q24 in, Q24 out 56 | static int32_t lookup(int32_t x); 57 | }; 58 | 59 | #define TANH_LG_N_SAMPLES 10 60 | #define TANH_N_SAMPLES (1 << TANH_LG_N_SAMPLES) 61 | 62 | extern int32_t tanhtab[TANH_N_SAMPLES << 1]; 63 | 64 | inline 65 | int32_t Tanh::lookup(int32_t x) { 66 | int32_t signum = x >> 31; 67 | x ^= signum; 68 | if (x >= (4 << 24)) { 69 | if (x >= (17 << 23)) { 70 | return signum ^ (1 << 24); 71 | } 72 | int32_t sx = ((int64_t)-48408812 * (int64_t)x) >> 24; 73 | return signum ^ ((1 << 24) - 2 * Exp2::lookup(sx)); 74 | } else { 75 | const int SHIFT = 26 - TANH_LG_N_SAMPLES; 76 | int lowbits = x & ((1 << SHIFT) - 1); 77 | int x_int = (x >> (SHIFT - 1)) & ((TANH_N_SAMPLES - 1) << 1); 78 | int dy = tanhtab[x_int]; 79 | int y0 = tanhtab[x_int + 1]; 80 | int y = y0 + (((int64_t)dy * (int64_t)lowbits) >> SHIFT); 81 | return y ^ signum; 82 | } 83 | } 84 | 85 | } -------------------------------------------------------------------------------- /app/instrument/DrumMachineWindow.cpp: -------------------------------------------------------------------------------- 1 | #include "DrumMachineWindow.hpp" 2 | 3 | #include "../App.hpp" 4 | #include "../vendor/imgui/imgui.h" 5 | #include "../vendor/imgui-knobs/imgui-knobs.h" 6 | #include "../../engine/core/Text.hpp" 7 | #include "../../engine/core/Log.hpp" 8 | 9 | 10 | 11 | namespace sns { 12 | 13 | 14 | DrumMachineWindow::DrumMachineWindow() { 15 | TAG = "DrumMachineWindow"; 16 | m_window_name = "Drum Machine"; 17 | m_subtype = InstrumentIdDrumMachine; 18 | m_is_instrument = true; 19 | m_has_presets = true; 20 | } 21 | 22 | DrumMachineWindow::~DrumMachineWindow() { 23 | 24 | } 25 | 26 | void DrumMachineWindow::render() { 27 | beforeRender(); 28 | ImGui::Begin(m_window_name.c_str(), &m_showing, ImGuiWindowFlags_NoResize); 29 | 30 | 31 | float height = ImGui::GetFontSize() * 25.0f; 32 | 33 | 34 | { 35 | float width = ImGui::GetFontSize() * 16.0f; 36 | ImGui::BeginChild("drum_machine_", ImVec2(width, height), true); 37 | ImGui::TextDisabled("KITS"); 38 | 39 | renderTable("TR-808", DrumMachine::tr808()); 40 | ImGui::SameLine(); 41 | renderTable("TR-909", DrumMachine::tr909()); 42 | ImGui::EndChild(); 43 | } 44 | 45 | ImGui::SameLine(); 46 | 47 | // 48 | // options 49 | // 50 | 51 | { 52 | float width = ImGui::GetFontSize() * 6.4f; 53 | 54 | ImGui::BeginChild("options_block", ImVec2(width, height), true); 55 | renderOptions(); 56 | ImGui::EndChild(); 57 | } 58 | 59 | //ImGui::SameLine(); 60 | //renderPresets(presets_height); 61 | 62 | 63 | aboutToFinishRender(); 64 | ImGui::End(); 65 | } 66 | 67 | 68 | 69 | void DrumMachineWindow::renderTable(std::string const& name, std::vector const& data) { 70 | ImGuiTableFlags flags = ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX; 71 | 72 | if (ImGui::BeginTable(name.c_str(), 2, flags)) { 73 | float c_width = ImGui::GetFontSize() * 5.0f; 74 | 75 | ImGui::TableSetupColumn(""); 76 | ImGui::TableSetupColumn(name.c_str(), ImGuiTableColumnFlags_WidthFixed, c_width); 77 | ImGui::TableHeadersRow(); 78 | 79 | for (auto const& [key, name, alias] : data) { 80 | ImGui::TableNextRow(); 81 | ImGui::TableSetColumnIndex(0); 82 | ImGui::TextUnformatted(noteName(key, true).c_str()); 83 | ImGui::TableSetColumnIndex(1); 84 | ImGui::Text("%s", name.c_str()); 85 | } 86 | 87 | ImGui::EndTable(); 88 | } 89 | } 90 | 91 | void DrumMachineWindow::renderOptions() { 92 | ImGui::TextDisabled("OPTIONS"); 93 | pKnob("Volume", ParameterVolume); 94 | } 95 | 96 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Senos 2 | Senos is sound exploration tool from a developer point of view. 3 | It is very lightweight, can be used as a toy or a minimal song composer. 4 | 5 | Play Setup | Instruments | Sequencer | Chainer | Analyser 6 | :---------:|:-----------:|:---------:|:---------:|:---------: 7 | ![Play](https://raw.githubusercontent.com/RuiVarela/Senos/main/docs/00.png) | ![Instruments](https://raw.githubusercontent.com/RuiVarela/Senos/main/docs/01.png) | ![Sequencer](https://raw.githubusercontent.com/RuiVarela/Senos/main/docs/02.png) | ![Chainer](https://raw.githubusercontent.com/RuiVarela/Senos/main/docs/03.png) | ![Analyser](https://raw.githubusercontent.com/RuiVarela/Senos/main/docs/04.png) 8 | 9 | 10 | ## Features 11 | - 4 playable instruments 12 | - SynthMachine, a minimal synthesizer 13 | - Dx7 FM synthesizer 14 | - 303 15 | - DrumMachine, 808 909 16 | - Step Sequencer 17 | - Chainer (sequencer chaining) 18 | - Software keyboard 19 | - Midi Support 20 | - Wav Recording 21 | - Projects minimal management 22 | - A couple of [demo songs](https://github.com/RuiVarela/Senos/tree/main/songs) 23 | 24 | ## Development 25 | ```bash 26 | git git@github.com:RuiVarela/Senos.git 27 | cd Senos 28 | 29 | mkdir build 30 | cd build 31 | 32 | # General 33 | cmake .. 34 | cmake --build . 35 | 36 | # To build a Release version on Mac: 37 | cmake -DCMAKE_BUILD_TYPE=Release .. 38 | cmake -DCMAKE_BUILD_TYPE=MinSizeRel .. 39 | cmake --build . -- -j 8 40 | 41 | # Build a release version for mac with arm support 42 | cmake -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" -DCMAKE_BUILD_TYPE=Release .. 43 | cmake --build . -- -j 8 44 | lipo -archs Senos.app/Contents/MacOS/Senos 45 | 46 | # To build a Release version on Windows with the VisualStudio toolchain: 47 | cmake .. 48 | cmake --build . --config Release 49 | cmake --build . --config MinSizeRel 50 | ``` 51 | 52 | # Credits 53 | - [sokol](https://github.com/floooh/sokol) 54 | - [imgui](https://github.com/ocornut/imgui) 55 | - [imgui-knobs](https://github.com/altschuler/imgui-knobs) 56 | - [tinyformat](https://github.com/c42f/tinyformat) 57 | - [nlohmann json](https://github.com/nlohmann/json) 58 | - [libremidi](https://github.com/jcelerier/libremidi) 59 | - [TinySoundFont](https://github.com/schellingb/TinySoundFont) 60 | - [martin-finke.de](http://martin-finke.de/) 61 | - [musicdsp](https://www.musicdsp.org/) 62 | - [msfa dx7](https://github.com/google/music-synthesizer-for-android) 63 | - [open303](https://github.com/maddanio/open303) 64 | - [microtar](https://github.com/rxi/microtar) 65 | - [miniz-cpp](https://github.com/tfussell/miniz-cpp) 66 | -------------------------------------------------------------------------------- /engine/instrument/dx7/env.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* 3 | * Copyright 2017 Pascal Gauthier. 4 | * Copyright 2012 Google Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | #include "synth.h" 20 | 21 | namespace dx7 { 22 | 23 | // DX7 envelope generation 24 | 25 | #define ACCURATE_ENVELOPE 26 | 27 | class Env { 28 | public: 29 | 30 | // The rates and levels arrays are calibrated to match the Dx7 parameters 31 | // (ie, value 0..99). The outlevel parameter is calibrated in microsteps 32 | // (ie units of approx .023 dB), with 99 * 32 = nominal full scale. The 33 | // rate_scaling parameter is in qRate units (ie 0..63). 34 | void init(const int rates[4], const int levels[4], int outlevel, int rate_scaling); 35 | 36 | void update(const int rates[4], const int levels[4], int outlevel, int rate_scaling); 37 | // Result is in Q24/doubling log format. Also, result is subsampled 38 | // for every N samples. 39 | // A couple more things need to happen for this to be used as a gain 40 | // value. First, the # of outputs scaling needs to be applied. Also, 41 | // modulation. 42 | // Then, of course, log to linear. 43 | int32_t getsample(); 44 | 45 | void keydown(bool down); 46 | static int scaleoutlevel(int outlevel); 47 | void getPosition(char *step); 48 | 49 | static void init_sr(double sample_rate); 50 | void transfer(Env &src); 51 | 52 | private: 53 | 54 | // PG: This code is normalized to 44100, need to put a multiplier 55 | // if we are not using 44100. 56 | static uint32_t sr_multiplier; 57 | 58 | int rates_[4]; 59 | int levels_[4]; 60 | int outlevel_; 61 | int rate_scaling_; 62 | // Level is stored so that 2^24 is one doubling, ie 16 more bits than 63 | // the DX7 itself (fraction is stored in level rather than separate 64 | // counter) 65 | int32_t level_; 66 | int targetlevel_; 67 | bool rising_; 68 | int ix_; 69 | int inc_; 70 | #ifdef ACCURATE_ENVELOPE 71 | int staticcount_; 72 | #endif 73 | 74 | bool down_; 75 | 76 | void advance(int newix); 77 | }; 78 | 79 | } 80 | 81 | -------------------------------------------------------------------------------- /engine/core/Worker.cpp: -------------------------------------------------------------------------------- 1 | #include "Worker.hpp" 2 | 3 | namespace sns { 4 | 5 | WaitEvent::WaitEvent(bool set) 6 | :m_mutex(), 7 | m_condition(), 8 | m_set_state(set), 9 | m_canceled(false) 10 | { 11 | } 12 | 13 | WaitEvent::~WaitEvent() noexcept { 14 | cancel(); 15 | } 16 | 17 | 18 | void WaitEvent::set() { 19 | std::unique_lock lock(m_mutex); 20 | if (m_canceled) 21 | return; 22 | 23 | m_set_state = true; 24 | m_condition.notify_all(); 25 | } 26 | 27 | bool WaitEvent::waitAndReset(int max_period_ms) { 28 | std::unique_lock lock(m_mutex); 29 | 30 | if (!m_set_state && !m_canceled) { 31 | auto wait = std::chrono::milliseconds(max_period_ms); 32 | m_condition.wait_for(lock, wait, [this]() { return m_set_state || m_canceled; }); 33 | } 34 | 35 | bool state = m_set_state; 36 | m_set_state = false; 37 | return state; 38 | } 39 | 40 | void WaitEvent::cancel() { 41 | std::unique_lock lock(m_mutex); 42 | m_canceled = true; 43 | m_condition.notify_all(); 44 | } 45 | 46 | 47 | 48 | Worker::Worker() 49 | :m_working(false) 50 | { 51 | setSleepMs(1000); 52 | } 53 | 54 | Worker::~Worker() { 55 | stopWorking(); 56 | } 57 | 58 | void Worker::setSleepMs(int millis) { 59 | m_sleep_milliseconds = millis; 60 | } 61 | 62 | void Worker::startWorking() { 63 | std::unique_lock lock(m_runner_mutex); 64 | 65 | stopWorking(); 66 | 67 | m_working = true; 68 | m_runner = std::thread(&Worker::pump, this); 69 | } 70 | 71 | void Worker::stopWorking() { 72 | std::unique_lock lock(m_runner_mutex); 73 | 74 | signalWorkEnd(); 75 | 76 | if (m_runner.joinable()) { 77 | signalWorkArrived(); 78 | m_runner.join(); 79 | } 80 | } 81 | 82 | bool Worker::isWorking() { 83 | return m_working; 84 | } 85 | 86 | void Worker::signalWorkEnd() { 87 | m_working = false; 88 | } 89 | 90 | void Worker::signalWorkArrived() { 91 | m_runner_event.set(); 92 | } 93 | 94 | void Worker::pump() { 95 | preWork(); 96 | 97 | while (isWorking()) { 98 | 99 | workStep(); 100 | 101 | m_runner_event.waitAndReset(m_sleep_milliseconds); 102 | } 103 | 104 | postWork(); 105 | } 106 | 107 | void Worker::workStep() { } 108 | void Worker::preWork() { } 109 | void Worker::postWork() { } 110 | 111 | 112 | void Worker::threadSleep(const int ms) { 113 | std::this_thread::sleep_for(std::chrono::milliseconds(ms)); 114 | } 115 | 116 | void Worker::threadSpin() { 117 | threadSleep(0); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /engine/instrument/tb303/rosic_MidiNoteEvent.h: -------------------------------------------------------------------------------- 1 | #ifndef rosic_MidiNoteEvent_h 2 | #define rosic_MidiNoteEvent_h 3 | 4 | namespace rosic 5 | { 6 | 7 | /** 8 | 9 | This is a class for representing MIDI note-events. 10 | 11 | */ 12 | 13 | class MidiNoteEvent 14 | { 15 | 16 | public: 17 | 18 | //--------------------------------------------------------------------------------------------- 19 | // construction/destruction: 20 | 21 | /** Default constructor. */ 22 | MidiNoteEvent(); 23 | 24 | /** Constructor with initializations. */ 25 | MidiNoteEvent(int initKey, int initVel, int initDetune = 0, int initPriority = 0 ); 26 | 27 | /** Destructor. */ 28 | ~MidiNoteEvent(); 29 | 30 | //--------------------------------------------------------------------------------------------- 31 | // parameter settings: 32 | 33 | /** Sets the key of the note (as MIDI note number between 0...127). */ 34 | void setKey(int newKey); 35 | 36 | /** Sets the velocity of the note (between 0...127). */ 37 | void setVelocity(int newVelocity); 38 | 39 | /** Sets the detuning of the note (in semitones). */ 40 | void setDetune(double newDetune); 41 | 42 | /** Sets the priority of the note. */ 43 | void setPriority(int newPriority); 44 | 45 | //--------------------------------------------------------------------------------------------- 46 | // inquiry: 47 | 48 | /** Returns the key of the note (as MIDI note number between 0...127). */ 49 | int getKey() const { return key; } 50 | 51 | /** Returns the velocity of the note (between 0...127). */ 52 | int getVelocity() const { return vel; } 53 | 54 | /** Returns the detuning of the note (in semitones). */ 55 | double getDetune() const { return detune; } 56 | 57 | /** Returns the priority of the note. */ 58 | int getPriority() const { return priority; } 59 | 60 | //--------------------------------------------------------------------------------------------- 61 | // overloaded operators: 62 | 63 | /** Note events are interpreted as equal if the have the same key. */ 64 | bool operator==(const MidiNoteEvent& note2) const 65 | { 66 | return note2.key == key; 67 | } 68 | 69 | protected: 70 | 71 | int key; // key of the note in the range 0...127 72 | int vel; // velocity of the note in the range 0...127 73 | double detune; // detuning in cents (for microtuning) 74 | int priority; // a priority value 75 | 76 | }; 77 | 78 | } // end namespace rosic 79 | 80 | #endif // MidiNoteEvent_h 81 | -------------------------------------------------------------------------------- /engine/instrument/tb303/rosic_BlendOscillator.cpp: -------------------------------------------------------------------------------- 1 | #include "rosic_BlendOscillator.h" 2 | using namespace rosic; 3 | 4 | //------------------------------------------------------------------------------------------------- 5 | // construction/destruction: 6 | 7 | BlendOscillator::BlendOscillator() 8 | { 9 | // init member variables: 10 | tableLengthDbl = (double) MipMappedWaveTable::tableLength; // typecasted version 11 | sampleRate = 44100.0; 12 | freq = 440.0; 13 | increment = (tableLengthDbl*freq)/sampleRate; 14 | phaseIndex = 0.0; 15 | startIndex = 0.0; 16 | waveTable1 = NULL; 17 | waveTable2 = NULL; 18 | 19 | // somewhat redundant: 20 | setSampleRate(44100.0); // sampleRate = 44100 Hz by default 21 | setFrequency (440.0); // frequency = 440 Hz by default 22 | setStartPhase(0.0); // sartPhase = 0 by default 23 | 24 | setWaveForm1(MipMappedWaveTable::SAW); 25 | setWaveForm2(MipMappedWaveTable::SQUARE); 26 | 27 | resetPhase(); 28 | } 29 | 30 | BlendOscillator::~BlendOscillator() 31 | { 32 | 33 | } 34 | 35 | //------------------------------------------------------------------------------------------------- 36 | // parameter settings: 37 | 38 | void BlendOscillator::setSampleRate(double newSampleRate) 39 | { 40 | if( newSampleRate > 0.0 ) 41 | sampleRate = newSampleRate; 42 | sampleRateRec = 1.0 / sampleRate; 43 | increment = tableLengthDbl*freq*sampleRateRec; 44 | } 45 | 46 | void BlendOscillator::setWaveForm1(int newWaveForm1) 47 | { 48 | if( waveTable1 != NULL ) 49 | waveTable1->setWaveform(newWaveForm1); 50 | } 51 | 52 | void BlendOscillator::setWaveForm2(int newWaveForm2) 53 | { 54 | if( waveTable2 != NULL ) 55 | waveTable2->setWaveform(newWaveForm2); 56 | } 57 | 58 | void BlendOscillator::setWaveTable1(MipMappedWaveTable* newWaveTable1) 59 | { 60 | waveTable1 = newWaveTable1; 61 | } 62 | 63 | void BlendOscillator::setWaveTable2(MipMappedWaveTable* newWaveTable2) 64 | { 65 | waveTable2 = newWaveTable2; 66 | } 67 | 68 | void BlendOscillator::setStartPhase(double StartPhase) 69 | { 70 | if( (StartPhase>=0) && (StartPhase<=360) ) 71 | startIndex = (StartPhase/360.0)*tableLengthDbl; 72 | } 73 | 74 | //------------------------------------------------------------------------------------------------- 75 | // event processing: 76 | 77 | void BlendOscillator::resetPhase() 78 | { 79 | phaseIndex = startIndex; 80 | } 81 | 82 | void BlendOscillator::setPhase(double PhaseIndex) 83 | { 84 | phaseIndex = startIndex+PhaseIndex; 85 | } 86 | -------------------------------------------------------------------------------- /app/tools/VUMeterWindow.cpp: -------------------------------------------------------------------------------- 1 | #include "VUMeterWindow.hpp" 2 | 3 | #include "../App.hpp" 4 | #include "../engine/core/Log.hpp" 5 | #include "../vendor/imgui/imgui.h" 6 | 7 | namespace sns { 8 | 9 | VUMeterWindow::VUMeterWindow() 10 | { 11 | TAG = "VU"; 12 | m_window_name = "VU Meter"; 13 | m_levels = 24; 14 | } 15 | 16 | VUMeterWindow::~VUMeterWindow() { 17 | 18 | } 19 | 20 | void VUMeterWindow::render(){ 21 | beforeRender(); 22 | 23 | Analyser& analyser = app()->engine().analyser(); 24 | 25 | ImGui::Begin("VU", &m_showing, ImGuiWindowFlags_NoResize); 26 | 27 | if (m_showing && !analyser.isAccepting()) 28 | analyser.start(TAG); 29 | 30 | const float current_linear = analyser.peak(); 31 | const float current_db = toDb(current_linear); 32 | const std::string text = sfmt("%05.1f db", current_db); 33 | 34 | const float base_size = ImGui::GetTextLineHeightWithSpacing(); 35 | const float h = ImGui::GetTextLineHeight(); 36 | const float w = h * 2.3f; 37 | 38 | const float step0 = 21.0f / 30.0f; 39 | const float step1 = 27.0f / 30.0f; 40 | const float step_delta = 1.0f / float(m_levels); 41 | 42 | ImGui::SetCursorPosX((ImGui::GetWindowWidth() - ImGui::CalcTextSize(text.c_str()).x) / 2.f); 43 | ImGui::Text("%s", text.c_str()); 44 | 45 | ImGui::BeginChild("bars_block", ImVec2(w, m_levels * base_size + base_size), false); 46 | { 47 | ImGui::Dummy(ImVec2(base_size, h/3.0f)); 48 | 49 | for (int i = 0; i != m_levels; ++i) { 50 | float f_start = float(m_levels - i - 1) * step_delta; 51 | float alpha = (current_linear > f_start) ? 1.0f : 0.2f; 52 | ImVec4 color; 53 | if (f_start >= step1) { 54 | color = ImVec4(1.0f, 0.0f, 0.0f, alpha); 55 | } else if (f_start >= step0) { 56 | color = ImVec4(1.0f, 1.0f, 0.0f, alpha); 57 | } else { 58 | color = ImVec4(0.0f, 1.0f, 0.0f, alpha); 59 | } 60 | 61 | ImVec2 p = ImGui::GetCursorScreenPos(); 62 | ImGui::GetWindowDrawList()->AddRectFilled(p, ImVec2(p.x + w, p.y + h), ImGui::ColorConvertFloat4ToU32(color)); 63 | ImGui::Dummy(ImVec2(w, h)); 64 | 65 | } 66 | } 67 | ImGui::EndChild(); 68 | 69 | ImGui::SameLine(); 70 | 71 | ImGui::BeginChild("labels_block", ImVec2(base_size * 1.8f, (m_levels + 1) * base_size), false); 72 | { 73 | for (int i = 0; i != (m_levels + 1); ++i) { 74 | float factor = float(m_levels - i) * step_delta; 75 | float db = toDb(factor); 76 | if (equivalent(db, 0.0f)) 77 | ImGui::Text(" 0"); 78 | else 79 | ImGui::Text("%05.1f", db); 80 | } 81 | } 82 | ImGui::EndChild(); 83 | 84 | 85 | 86 | if (!m_showing && analyser.isAccepting()) 87 | analyser.stop(TAG); 88 | 89 | aboutToFinishRender(); 90 | ImGui::End(); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /vendor/libremidi/reader.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Dimitri Diakopoulos All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | #pragma once 27 | 28 | #include 29 | 30 | namespace libremidi 31 | { 32 | /** 33 | * @brief reads Standard MIDI files (SMF). 34 | * 35 | * Usage: 36 | 37 | * ``` 38 | * libremidi::reader r; 39 | * auto res = r.parse(midi_bytes, num_bytes); 40 | * ``` 41 | */ 42 | class reader 43 | { 44 | public: 45 | enum parse_result { 46 | invalid, //! Nothing could be parsed 47 | incomplete, //! Some of the data could be parsed, but not all: there may be missing events / tracks 48 | complete, //! All the data could be parsed but not necessarily validated 49 | validated //! The data could be parsed and conforms to SMF rules 50 | }; 51 | explicit reader(bool useAbsolute = false); 52 | ~reader(); 53 | 54 | parse_result parse(const uint8_t* data, std::size_t size) noexcept; 55 | parse_result parse(const std::vector& buffer) noexcept; 56 | #if defined(LIBREMIDI_HAS_SPAN) 57 | parse_result parse(std::span buffer) noexcept; 58 | #endif 59 | 60 | double get_end_time(); 61 | 62 | float ticksPerBeat{}; // precision (number of ticks distinguishable per second) 63 | float startingTempo{}; 64 | int format{}; 65 | 66 | std::vector tracks; 67 | 68 | private: 69 | bool useAbsoluteTicks{}; 70 | }; 71 | } 72 | 73 | #if defined(LIBREMIDI_HEADER_ONLY) 74 | # include 75 | #endif 76 | -------------------------------------------------------------------------------- /vendor/libremidi/detail/emscripten_api.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if defined(__EMSCRIPTEN__) 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace libremidi 12 | { 13 | class observer_emscripten; 14 | class midi_in_emscripten; 15 | namespace webmidi_helpers 16 | { 17 | struct device_information 18 | { 19 | std::string id; 20 | std::string name; 21 | bool connected{}; 22 | }; 23 | } 24 | 25 | class observer_emscripten final : public observer_api 26 | { 27 | using device = webmidi_helpers::device_information; 28 | public: 29 | observer_emscripten(observer::callbacks&& c); 30 | ~observer_emscripten(); 31 | 32 | void update(const std::vector& current_inputs, const std::vector& current_outputs); 33 | 34 | private: 35 | std::vector m_known_inputs; 36 | std::vector m_known_outputs; 37 | }; 38 | 39 | class midi_in_emscripten final : public midi_in_default 40 | { 41 | public: 42 | static const constexpr auto backend = "Emscripten"; 43 | 44 | midi_in_emscripten(std::string_view clientName, unsigned int queueSizeLimit); 45 | ~midi_in_emscripten() override; 46 | 47 | libremidi::API get_current_api() const noexcept override; 48 | 49 | void open_port(unsigned int portNumber, std::string_view) override; 50 | void close_port() override; 51 | 52 | void set_client_name(std::string_view clientName) override; 53 | void set_port_name(std::string_view portName) override; 54 | 55 | void on_input(message msg); 56 | 57 | unsigned int get_port_count() override; 58 | std::string get_port_name(unsigned int portNumber) override; 59 | 60 | private: 61 | int portNumber_{}; 62 | }; 63 | 64 | class midi_out_emscripten final : public midi_out_default 65 | { 66 | public: 67 | static const constexpr auto backend = "Raw ALSA"; 68 | 69 | midi_out_emscripten(std::string_view); 70 | ~midi_out_emscripten() override; 71 | 72 | libremidi::API get_current_api() const noexcept override; 73 | 74 | void open_port(unsigned int portNumber, std::string_view) override; 75 | void close_port() override; 76 | 77 | void set_client_name(std::string_view clientName) override; 78 | void set_port_name(std::string_view portName) override; 79 | 80 | unsigned int get_port_count() override; 81 | std::string get_port_name(unsigned int portNumber) override; 82 | 83 | void send_message(const unsigned char* message, size_t size) override; 84 | 85 | private: 86 | int portNumber_{}; 87 | }; 88 | 89 | } 90 | extern "C" 91 | { 92 | EMSCRIPTEN_KEEPALIVE 93 | void libremidi_devices_poll(); 94 | 95 | EMSCRIPTEN_KEEPALIVE 96 | void libremidi_devices_input(int port, double timestamp, int len, char* bytes); 97 | } 98 | #endif 99 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cmake.configureOnOpen": true, 3 | "files.associations": { 4 | "__bit_reference": "cpp", 5 | "__bits": "cpp", 6 | "__config": "cpp", 7 | "__debug": "cpp", 8 | "__errc": "cpp", 9 | "__hash_table": "cpp", 10 | "__locale": "cpp", 11 | "__mutex_base": "cpp", 12 | "__node_handle": "cpp", 13 | "__nullptr": "cpp", 14 | "__split_buffer": "cpp", 15 | "__string": "cpp", 16 | "__threading_support": "cpp", 17 | "__tuple": "cpp", 18 | "array": "cpp", 19 | "atomic": "cpp", 20 | "bit": "cpp", 21 | "bitset": "cpp", 22 | "cctype": "cpp", 23 | "chrono": "cpp", 24 | "clocale": "cpp", 25 | "cmath": "cpp", 26 | "compare": "cpp", 27 | "complex": "cpp", 28 | "concepts": "cpp", 29 | "cstdarg": "cpp", 30 | "cstddef": "cpp", 31 | "cstdint": "cpp", 32 | "cstdio": "cpp", 33 | "cstdlib": "cpp", 34 | "cstring": "cpp", 35 | "ctime": "cpp", 36 | "cwchar": "cpp", 37 | "cwctype": "cpp", 38 | "exception": "cpp", 39 | "initializer_list": "cpp", 40 | "ios": "cpp", 41 | "iosfwd": "cpp", 42 | "istream": "cpp", 43 | "limits": "cpp", 44 | "locale": "cpp", 45 | "memory": "cpp", 46 | "mutex": "cpp", 47 | "new": "cpp", 48 | "optional": "cpp", 49 | "ostream": "cpp", 50 | "ratio": "cpp", 51 | "sstream": "cpp", 52 | "stdexcept": "cpp", 53 | "streambuf": "cpp", 54 | "string": "cpp", 55 | "string_view": "cpp", 56 | "system_error": "cpp", 57 | "tuple": "cpp", 58 | "type_traits": "cpp", 59 | "typeinfo": "cpp", 60 | "unordered_map": "cpp", 61 | "variant": "cpp", 62 | "vector": "cpp", 63 | "__functional_base": "cpp", 64 | "algorithm": "cpp", 65 | "functional": "cpp", 66 | "iterator": "cpp", 67 | "utility": "cpp", 68 | "__tree": "cpp", 69 | "codecvt": "cpp", 70 | "forward_list": "cpp", 71 | "iomanip": "cpp", 72 | "iostream": "cpp", 73 | "map": "cpp", 74 | "numeric": "cpp", 75 | "valarray": "cpp", 76 | "list": "cpp", 77 | "condition_variable": "cpp", 78 | "thread": "cpp", 79 | "deque": "cpp", 80 | "fstream": "cpp", 81 | "stack": "cpp", 82 | "version": "cpp", 83 | "memory_resource": "cpp", 84 | "random": "cpp", 85 | "__memory": "cpp", 86 | "filesystem": "cpp", 87 | "set": "cpp", 88 | "cinttypes": "cpp", 89 | "span": "cpp", 90 | "execution": "cpp", 91 | "ranges": "cpp" 92 | } 93 | } -------------------------------------------------------------------------------- /engine/instrument/dx7/dx7note.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* 3 | * Copyright 2016-2017 Pascal Gauthier. 4 | * Copyright 2012 Google Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | // This is the logic to put together a note from the MIDI description 20 | // and run the low-level modules. 21 | 22 | // It will continue to evolve a bit, as note-stealing logic, scaling, 23 | // and real-time control of parameters live here. 24 | 25 | #include "env.h" 26 | #include "pitchenv.h" 27 | #include "fm_core.h" 28 | #include "tuning.h" 29 | 30 | namespace dx7 { 31 | 32 | struct VoiceStatus { 33 | uint32_t amp[6]; 34 | char ampStep[6]; 35 | char pitchStep; 36 | }; 37 | 38 | class Dx7Note { 39 | public: 40 | Dx7Note(std::shared_ptr ts); 41 | void init(const uint8_t patch[156], int midinote, int velocity); 42 | 43 | // Note: this _adds_ to the buffer. Interesting question whether it's 44 | // worth it... 45 | void compute(int32_t *buf, int32_t lfo_val, int32_t lfo_delay, 46 | const Controllers *ctrls); 47 | 48 | void keyup(); 49 | 50 | // TODO: some way of indicating end-of-note. Maybe should be a return 51 | // value from the compute method? (Having a count return from keyup 52 | // is also tempting, but if there's a dynamic parameter change after 53 | // keyup, that won't work. 54 | 55 | // PG:add the update 56 | void update(const uint8_t patch[156], int midinote, int velocity); 57 | void peekVoiceStatus(VoiceStatus &status); 58 | void transferState(Dx7Note& src); 59 | void transferSignal(Dx7Note &src); 60 | void oscSync(); 61 | 62 | int32_t osc_freq(int midinote, int mode, int coarse, int fine, int detune); 63 | 64 | std::shared_ptr tuning_state_; 65 | 66 | int mpePitchBend = 8192; 67 | int mpePressure = 0; 68 | int mpeTimbre = 0; 69 | 70 | private: 71 | //FmCore core_; 72 | Env env_[6]; 73 | FmOpParams params_[6]; 74 | PitchEnv pitchenv_; 75 | int32_t basepitch_[6]; 76 | int32_t fb_buf_[2]; 77 | int32_t fb_shift_; 78 | int32_t ampmodsens_[6]; 79 | int32_t opMode[6]; 80 | 81 | uint8_t playingMidiNote; // We need this for scale aware pitch bend 82 | 83 | int ampmoddepth_; 84 | int algorithm_; 85 | int pitchmoddepth_; 86 | int pitchmodsens_; 87 | 88 | }; 89 | 90 | } 91 | -------------------------------------------------------------------------------- /vendor/libremidi/detail/dummy.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace libremidi 6 | { 7 | class observer_dummy final : public observer_api 8 | { 9 | public: 10 | observer_dummy(observer::callbacks&& c) : observer_api{std::move(c)} 11 | { 12 | } 13 | 14 | ~observer_dummy() 15 | { 16 | } 17 | }; 18 | 19 | class midi_in_dummy final : public midi_in_api 20 | { 21 | public: 22 | midi_in_dummy(std::string_view /*clientName*/, unsigned int queueSizeLimit) 23 | : midi_in_api{nullptr, queueSizeLimit} 24 | { 25 | warning("midi_in_dummy: This class provides no functionality."); 26 | } 27 | 28 | libremidi::API get_current_api() const noexcept override 29 | { 30 | return libremidi::API::DUMMY; 31 | } 32 | 33 | void open_port(unsigned int /*portNumber*/, std::string_view /*portName*/) override 34 | { 35 | } 36 | 37 | void open_virtual_port(std::string_view /*portName*/) override 38 | { 39 | } 40 | 41 | void close_port() override 42 | { 43 | } 44 | 45 | void set_client_name(std::string_view /*clientName*/) override 46 | { 47 | } 48 | 49 | void set_port_name(std::string_view /*portName*/) override 50 | { 51 | } 52 | 53 | unsigned int get_port_count() override 54 | { 55 | return 0; 56 | } 57 | 58 | std::string get_port_name(unsigned int /*portNumber*/) override 59 | { 60 | using namespace std::literals; 61 | return ""s; 62 | } 63 | }; 64 | 65 | class midi_out_dummy final : public midi_out_api 66 | { 67 | public: 68 | explicit midi_out_dummy(std::string_view /*clientName*/) 69 | { 70 | warning("midi_out_dummy: This class provides no functionality."); 71 | } 72 | 73 | libremidi::API get_current_api() const noexcept override 74 | { 75 | return libremidi::API::DUMMY; 76 | } 77 | 78 | void open_port(unsigned int /*portNumber*/, std::string_view /*portName*/) override 79 | { 80 | } 81 | void open_virtual_port(std::string_view /*portName*/) override 82 | { 83 | } 84 | void close_port() override 85 | { 86 | } 87 | void set_client_name(std::string_view /*clientName*/) override 88 | { 89 | } 90 | void set_port_name(std::string_view /*portName*/) override 91 | { 92 | } 93 | unsigned int get_port_count() override 94 | { 95 | return 0; 96 | } 97 | std::string get_port_name(unsigned int /*portNumber*/) override 98 | { 99 | using namespace std::literals; 100 | return ""s; 101 | } 102 | void send_message(const unsigned char* /*message*/, size_t /*size*/) override 103 | { 104 | } 105 | }; 106 | 107 | struct dummy_backend 108 | { 109 | using midi_in = midi_in_dummy; 110 | using midi_out = midi_out_dummy; 111 | using midi_observer = observer_dummy; 112 | static const constexpr auto API = libremidi::API::DUMMY; 113 | }; 114 | } 115 | -------------------------------------------------------------------------------- /engine/Sequencer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "audio/Audio.hpp" 4 | #include "instrument/Instrument.hpp" 5 | 6 | namespace sns { 7 | 8 | class Engine; 9 | 10 | class Sequencer { 11 | public: 12 | enum class NoteMode { 13 | Off, 14 | Press, 15 | Accent, 16 | Hold, 17 | 18 | Count 19 | }; 20 | 21 | enum class Action { 22 | None, 23 | Play, 24 | PlayOnce, 25 | Pause, 26 | TogglePlay, 27 | Stop, 28 | 29 | Count 30 | }; 31 | 32 | struct InstrumentConfiguration { 33 | using StepNote = std::pair; // step, note 34 | using Steps = std::map; 35 | 36 | Steps steps; 37 | bool muted = false; 38 | 39 | int ui_first_row = 0; 40 | int ui_first_col = 0; 41 | }; 42 | 43 | struct Configuration { 44 | float duty = 0.3f; 45 | int tempo = 90; 46 | int step_count = 16; 47 | 48 | std::array instruments; 49 | 50 | 51 | Action action = Action::None; 52 | int action_step = 0; 53 | 54 | int ui_selected_instrument = InstrumentStart; 55 | 56 | 57 | NoteMode stepState(InstrumentId instrument, int step, int note); 58 | void setStepState(InstrumentId instrument, int step, int note, NoteMode value); 59 | void toggleStepState(InstrumentId instrument, int step, int note); 60 | }; 61 | 62 | struct State { 63 | int active_step = 0; 64 | bool playing = false; 65 | bool once = false; 66 | bool chaining = false; 67 | }; 68 | 69 | Sequencer(); 70 | 71 | Sequencer(Sequencer const&) = delete; 72 | Sequencer& operator=(Sequencer const&) = delete; 73 | 74 | void setEngine(Engine* engine); 75 | 76 | State state(); 77 | bool next(); 78 | 79 | void apply(Configuration const& cfg, bool chaining = false); 80 | void panic(); 81 | private: 82 | std::string TAG; 83 | Engine* m_engine; 84 | 85 | State m_state; 86 | 87 | Configuration m_cfg; 88 | bool m_cfg_changed; 89 | int m_beat_samples; 90 | int m_duty_samples; 91 | int m_samples_since_last_beat; 92 | 93 | 94 | // lookup data 95 | using InstrumentNote = std::tuple; // instrument -> note -> notemode 96 | using InstrumentNotes = std::vector; 97 | using StepNotes = std::vector; 98 | StepNotes m_steps; 99 | InstrumentNotes m_playing_notes; 100 | 101 | void stopAllPlayingNotes(); 102 | void stopPlayingPreviousNotes(bool duty_end); 103 | void startPlayingNewNotes(); 104 | 105 | static NoteMode getNote(InstrumentId instrument, int note, InstrumentNotes const& search); 106 | NoteMode getStepNote(int step, InstrumentId instrument, int note); 107 | NoteMode getPlayingNote(InstrumentId instrument, int note); 108 | 109 | }; 110 | 111 | std::string toString(Sequencer::Action action); 112 | std::string toString(Sequencer::NoteMode mode); 113 | } -------------------------------------------------------------------------------- /app/App.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../engine/Engine.hpp" 4 | 5 | #include "Configuration.hpp" 6 | #include "Window.hpp" 7 | #include "tools/KeyboardWindow.hpp" 8 | #include "Platform.hpp" 9 | 10 | 11 | namespace sns { 12 | 13 | class App : public FeedbackCallback { 14 | public: 15 | std::string TAG; 16 | 17 | App(); 18 | ~App(); 19 | 20 | void onInstrumentParamsFeedback(InstrumentId instrument_id, ParametersValues const& values) override; 21 | void onMidiNotesFeedback(InstrumentId instrument_id, std::set const& notes_pressed) override; 22 | void onSequencerState(Sequencer::State state) override; 23 | void onChainerState(Chainer::State state) override; 24 | 25 | void initialize(); 26 | void cleanup(); 27 | void render(); 28 | 29 | bool isFullscreen() const; 30 | int width() const; 31 | int height() const; 32 | float dpiScale() const; 33 | uint64_t frameNumber() const; 34 | float filteredFps() const; 35 | 36 | std::vector> windows(); 37 | 38 | bool shouldRenderMenu(); 39 | InstrumentId activeInstrument(); 40 | 41 | Configuration& configuration(); 42 | Engine& engine(); 43 | 44 | int versionCode(); 45 | std::string versionName(); 46 | 47 | template 48 | std::shared_ptr getWindow() { 49 | for (auto& current : m_windows) { 50 | std::shared_ptr casted = std::dynamic_pointer_cast(current); 51 | if (casted) 52 | return casted; 53 | } 54 | return std::shared_ptr(); 55 | } 56 | private: 57 | Configuration m_configuration; 58 | Engine m_engine; 59 | std::vector> m_windows; 60 | std::vector m_tools; 61 | std::vector m_instruments; 62 | 63 | std::mutex m_actions_mutex; 64 | std::vector> m_actions; 65 | 66 | void setActiveInstrument(InstrumentId instrument); 67 | InstrumentId m_active_instrument; 68 | 69 | uint64_t m_frame; 70 | int64_t m_frame_ts; 71 | RunningAverage m_render_fps; 72 | bool m_fullscreen; 73 | 74 | void buildMenu(); 75 | void renderMainMenu(); 76 | void triggerMenuCommand(std::string const& command); 77 | void dispatchMenuCommands(); 78 | std::vector m_menu_commands; 79 | Menu m_menu; 80 | MenuShortcuts m_shortcuts; 81 | 82 | 83 | void windowCloseAll(); 84 | void windowLayoutRedistribute(); 85 | void windowLayoutCascade(); 86 | void windowLayoutPlay(); 87 | 88 | void loadConfiguration(); 89 | void projectNew(bool trigger); 90 | void projectOpen(bool trigger); 91 | void projectClone(bool trigger); 92 | void projectExport(); 93 | void projectImport(); 94 | 95 | void recordStart(); 96 | void recordStop(); 97 | 98 | std::string m_load_project; 99 | std::string m_clone_project; 100 | std::string m_import_project_file; 101 | 102 | friend Configuration migrateApp(App* app, Configuration cfg, int from, int to); 103 | }; 104 | } 105 | -------------------------------------------------------------------------------- /engine/instrument/tb303/rosic_AcidSequencer.cpp: -------------------------------------------------------------------------------- 1 | #include "rosic_AcidSequencer.h" 2 | using namespace rosic; 3 | 4 | //------------------------------------------------------------------------------------------------- 5 | // construction/destruction: 6 | 7 | AcidSequencer::AcidSequencer() 8 | { 9 | sampleRate = 44100.0; 10 | bpm = 140.0; 11 | activePattern = 0; 12 | running = false; 13 | countDown = 0; 14 | step = 0; 15 | sequencerMode = OFF; 16 | driftError = 0.0; 17 | modeChanged = false; 18 | 19 | for(int k=0; k<=12; k++) 20 | keyPermissible[k] = true; 21 | } 22 | 23 | //------------------------------------------------------------------------------------------------- 24 | // parameter settings: 25 | 26 | void AcidSequencer::setSampleRate(double newSampleRate) 27 | { 28 | if( newSampleRate > 0.0 ) 29 | sampleRate = newSampleRate; 30 | } 31 | 32 | void AcidSequencer::setMode(int newMode) 33 | { 34 | if( newMode >= 0 && newMode < NUM_SEQUENCER_MODES ) 35 | { 36 | sequencerMode = newMode; 37 | modeChanged = true; 38 | } 39 | } 40 | 41 | void AcidSequencer::setKeyPermissible(int key, bool shouldBePermissible) 42 | { 43 | if( key >= 0 && key <= 12 ) 44 | keyPermissible[key] = shouldBePermissible; 45 | } 46 | 47 | void AcidSequencer::toggleKeyPermissibility(int key) 48 | { 49 | if( key >= 0 && key <= 12 ) 50 | keyPermissible[key] = !keyPermissible[key]; 51 | } 52 | 53 | //------------------------------------------------------------------------------------------------- 54 | // inquiry: 55 | 56 | AcidPattern* AcidSequencer::getPattern(int index) 57 | { 58 | if( index < 0 || index >= numPatterns ) 59 | return NULL; 60 | else 61 | return &patterns[index]; 62 | } 63 | 64 | bool AcidSequencer::modeWasChanged() 65 | { 66 | bool result = modeChanged; 67 | modeChanged = false; 68 | return result; 69 | // mmm...wouldn't we need mutexes here? the mode changes from the GUI and modeWasChanged 70 | // is called from the audio-thread - otherwise note-hangs could happen? 71 | } 72 | 73 | bool AcidSequencer::isKeyPermissible(int key) 74 | { 75 | if( key >= 0 && key <= 12 ) 76 | return keyPermissible[key]; 77 | else 78 | return false; 79 | } 80 | 81 | //------------------------------------------------------------------------------------------------- 82 | // event handling: 83 | 84 | void AcidSequencer::start() 85 | { 86 | // set up members such that we will trap in the else-branch in the next call to getNote(): 87 | running = true; 88 | countDown = -1; 89 | step = 0; 90 | driftError = 0.0; 91 | } 92 | 93 | void AcidSequencer::stop() 94 | { 95 | running = false; 96 | } 97 | 98 | //------------------------------------------------------------------------------------------------- 99 | // others: 100 | -------------------------------------------------------------------------------- /vendor/imgui-fonts/fonts.h: -------------------------------------------------------------------------------- 1 | // Copyright The Authors 2018. 2 | // Distributed under the 3-Clause BSD License. 3 | // (See accompanying file LICENSE or copy at 4 | // https://opensource.org/licenses/BSD-3-Clause) 5 | 6 | #pragma once 7 | 8 | namespace asap { 9 | namespace debug { 10 | namespace ui { 11 | 12 | class Fonts { 13 | public: 14 | // Roboto Black 15 | static const unsigned int ROBOTO_BLACK_COMPRESSED_SIZE = 121781; 16 | static const unsigned int ROBOTO_BLACK_COMPRESSED_DATA[]; 17 | 18 | // Roboto Black Italic 19 | static const unsigned int ROBOTO_BLACKITALIC_COMPRESSED_SIZE = 130821; 20 | static const unsigned int ROBOTO_BLACKITALIC_COMPRESSED_DATA[]; 21 | 22 | // Roboto Bold 23 | static const unsigned int ROBOTO_BOLD_COMPRESSED_SIZE = 120826; 24 | static const unsigned int ROBOTO_BOLD_COMPRESSED_DATA[]; 25 | 26 | // Roboto Bold Italic 27 | static const unsigned int ROBOTO_BOLDITALIC_COMPRESSED_SIZE = 127464; 28 | static const unsigned int ROBOTO_BOLDITALIC_COMPRESSED_DATA[]; 29 | 30 | // Roboto Italic 31 | static const unsigned int ROBOTO_ITALIC_COMPRESSED_SIZE = 126291; 32 | static const unsigned int ROBOTO_ITALIC_COMPRESSED_DATA[]; 33 | 34 | // Roboto Light 35 | static const unsigned int ROBOTO_LIGHT_COMPRESSED_SIZE = 119387; 36 | static const unsigned int ROBOTO_LIGHT_COMPRESSED_DATA[]; 37 | 38 | // Roboto Light Italic 39 | static const unsigned int ROBOTO_LIGHTITALIC_COMPRESSED_SIZE = 127539; 40 | static const unsigned int ROBOTO_LIGHTITALIC_COMPRESSED_DATA[]; 41 | 42 | // Roboto Medium 43 | static const unsigned int ROBOTO_MEDIUM_COMPRESSED_SIZE = 121068; 44 | static const unsigned int ROBOTO_MEDIUM_COMPRESSED_DATA[]; 45 | 46 | // Roboto Medium Italic 47 | static const unsigned int ROBOTO_MEDIUMITALIC_COMPRESSED_SIZE = 129166; 48 | static const unsigned int ROBOTO_MEDIUMITALIC_COMPRESSED_DATA[]; 49 | 50 | // Roboto Regular 51 | static const unsigned int ROBOTO_REGULAR_COMPRESSED_SIZE = 119875; 52 | static const unsigned int ROBOTO_REGULAR_COMPRESSED_DATA[]; 53 | 54 | // Roboto Thin 55 | static const unsigned int ROBOTO_THIN_COMPRESSED_SIZE = 117636; 56 | static const unsigned int ROBOTO_THIN_COMPRESSED_DATA[]; 57 | 58 | // Roboto Thin Italic 59 | static const unsigned int ROBOTO_THINITALIC_COMPRESSED_SIZE = 125327; 60 | static const unsigned int ROBOTO_THINITALIC_COMPRESSED_DATA[]; 61 | 62 | // Inconsolata Regular 63 | static const unsigned int INCONSOLATA_REGULAR_COMPRESSED_SIZE = 64659; 64 | static const unsigned int INCONSOLATA_REGULAR_COMPRESSED_DATA[]; 65 | 66 | // Inconsolata Bold 67 | static const unsigned int INCONSOLATA_BOLD_COMPRESSED_SIZE = 72170; 68 | static const unsigned int INCONSOLATA_BOLD_COMPRESSED_DATA[]; 69 | 70 | // Material Design Icons 71 | static const unsigned int MATERIAL_DESIGN_ICONS_COMPRESSED_SIZE = 248964; 72 | static const unsigned int MATERIAL_DESIGN_ICONS_COMPRESSED_DATA[]; 73 | 74 | }; 75 | 76 | } // namespace ui 77 | } // namespace debug 78 | } // namespace asap 79 | -------------------------------------------------------------------------------- /engine/instrument/dx7/lfo.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Low frequency oscillator, compatible with DX7 18 | #include "synth.h" 19 | #include "sin.h" 20 | #include "lfo.h" 21 | 22 | namespace dx7 { 23 | 24 | uint32_t Lfo::unit_; 25 | 26 | void Lfo::init(double sample_rate) { 27 | // constant is 1 << 32 / 15.5s / 11 28 | Lfo::unit_ = (int32_t)(N * 25190424 / sample_rate + 0.5); 29 | } 30 | 31 | void Lfo::reset(const char params[6]) { 32 | int rate = params[0]; // 0..99 33 | int sr = rate == 0 ? 1 : (165 * rate) >> 6; 34 | sr *= sr < 160 ? 11 : (11 + ((sr - 160) >> 4)); 35 | delta_ = unit_ * sr; 36 | int a = 99 - params[1]; // LFO delay 37 | if (a == 99) { 38 | delayinc_ = ~0u; 39 | delayinc2_ = ~0u; 40 | } else { 41 | a = (16 + (a & 15)) << (1 + (a >> 4)); 42 | delayinc_ = unit_ * a; 43 | a &= 0xff80; 44 | a = std::max(0x80, a); 45 | delayinc2_ = unit_ * a; 46 | } 47 | waveform_ = params[5]; 48 | sync_ = params[4] != 0; 49 | } 50 | 51 | int32_t Lfo::getsample() { 52 | phase_ += delta_; 53 | int32_t x; 54 | switch (waveform_) { 55 | case 0: // triangle 56 | x = phase_ >> 7; 57 | x ^= -(phase_ >> 31); 58 | x &= (1 << 24) - 1; 59 | return x; 60 | case 1: // sawtooth down 61 | return (~phase_ ^ (1U << 31)) >> 8; 62 | case 2: // sawtooth up 63 | return (phase_ ^ (1U << 31)) >> 8; 64 | case 3: // square 65 | return ((~phase_) >> 7) & (1 << 24); 66 | case 4: // sine 67 | return (1 << 23) + (Sin::lookup(phase_ >> 8) >> 1); 68 | case 5: // s&h 69 | if (phase_ < delta_) { 70 | randstate_ = (randstate_ * 179 + 17) & 0xff; 71 | } 72 | x = randstate_ ^ 0x80; 73 | return (x + 1) << 16; 74 | } 75 | return 1 << 23; 76 | } 77 | 78 | int32_t Lfo::getdelay() { 79 | uint32_t delta = delaystate_ < (1U << 31) ? delayinc_ : delayinc2_; 80 | uint64_t d = ((uint64_t)delaystate_) + delta; 81 | if (d > ~0u) { 82 | return 1 << 24; 83 | } 84 | delaystate_ = d; 85 | if (d < (1U << 31)) { 86 | return 0; 87 | } else { 88 | return (d >> 7) & ((1 << 24) - 1); 89 | } 90 | } 91 | 92 | void Lfo::keydown() { 93 | if (sync_) { 94 | phase_ = (1U << 31) - 1; 95 | } 96 | delaystate_ = 0; 97 | } 98 | 99 | } -------------------------------------------------------------------------------- /engine/instrument/tb303/rosic_LeakyIntegrator.cpp: -------------------------------------------------------------------------------- 1 | #include "rosic_LeakyIntegrator.h" 2 | using namespace rosic; 3 | 4 | //------------------------------------------------------------------------------------------------- 5 | // construction/destruction: 6 | 7 | LeakyIntegrator::LeakyIntegrator() 8 | { 9 | sampleRate = 44100.0f; 10 | tau = 10.0f; 11 | y1 = 0.0; 12 | 13 | calculateCoefficient(); 14 | } 15 | 16 | LeakyIntegrator::~LeakyIntegrator() 17 | { 18 | 19 | } 20 | 21 | //------------------------------------------------------------------------------------------------- 22 | // parameter settings: 23 | 24 | void LeakyIntegrator::setSampleRate(double newSampleRate) 25 | { 26 | if( newSampleRate > 0.0 ) 27 | { 28 | sampleRate = newSampleRate; 29 | calculateCoefficient(); 30 | } 31 | } 32 | 33 | void LeakyIntegrator::setTimeConstant(double newTimeConstant) 34 | { 35 | if( newTimeConstant >= 0.0 && newTimeConstant != tau ) 36 | { 37 | tau = newTimeConstant; 38 | calculateCoefficient(); 39 | } 40 | } 41 | 42 | //------------------------------------------------------------------------------------------------- 43 | // inquiry: 44 | 45 | double LeakyIntegrator::getNormalizer(double tau1, double tau2, double fs) 46 | { 47 | double td = 0.001*tau1; 48 | double ta = 0.001*tau2; 49 | 50 | // catch some special cases: 51 | if( ta == 0.0 && td == 0.0 ) 52 | return 1.0; 53 | else if( ta == 0.0 ) 54 | { 55 | return 1.0 / (1.0-exp(-1.0/(fs*td))); 56 | } 57 | else if( td == 0.0 ) 58 | { 59 | return 1.0 / (1.0-exp(-1.0/(fs*ta))); 60 | } 61 | 62 | // compute the filter coefficients: 63 | double x = exp( -1.0 / (fs*td) ); 64 | double bd = 1-x; 65 | double ad = -x; 66 | x = exp( -1.0 / (fs*ta) ); 67 | double ba = 1-x; 68 | double aa = -x; 69 | 70 | // compute the location and height of the peak: 71 | double xp; 72 | if( ta == td ) 73 | { 74 | double tp = ta; 75 | double np = fs*tp; 76 | xp = (np+1.0)*ba*ba*pow(aa, np); 77 | } 78 | else 79 | { 80 | double tp = log(ta/td) / ( (1.0/td) - (1.0/ta) ); 81 | double np = fs*tp; 82 | double s = 1.0 / (aa-ad); 83 | double b01 = s * aa*ba*bd; 84 | double b02 = s * ad*ba*bd; 85 | double a01 = s * (ad-aa)*aa; 86 | double a02 = s * (ad-aa)*ad; 87 | xp = b01*pow(a01, np) - b02*pow(a02, np); 88 | } 89 | 90 | // return the normalizer as reciprocal of the peak height: 91 | return 1.0/xp; 92 | } 93 | 94 | //------------------------------------------------------------------------------------------------- 95 | // others: 96 | 97 | void LeakyIntegrator::reset() 98 | { 99 | y1 = 0; 100 | } 101 | 102 | void LeakyIntegrator::calculateCoefficient() 103 | { 104 | if( tau > 0.0 ) 105 | coeff = exp( -1.0 / (sampleRate*0.001*tau) ); 106 | else 107 | coeff = 0.0; 108 | } 109 | 110 | -------------------------------------------------------------------------------- /engine/instrument/tb303/rosic_LeakyIntegrator.h: -------------------------------------------------------------------------------- 1 | #ifndef rosic_LeakyIntegrator_h 2 | #define rosic_LeakyIntegrator_h 3 | 4 | // rosic-indcludes: 5 | #include "rosic_RealFunctions.h" 6 | 7 | namespace rosic 8 | { 9 | 10 | /** 11 | 12 | This is a leaky integrator type lowpass filter with user adjustable time constant which is set 13 | up in milliseconds. 14 | 15 | */ 16 | 17 | class LeakyIntegrator 18 | { 19 | 20 | public: 21 | 22 | //--------------------------------------------------------------------------------------------- 23 | // construction/destruction: 24 | 25 | /** Constructor. */ 26 | LeakyIntegrator(); 27 | 28 | /** Destructor. */ 29 | ~LeakyIntegrator(); 30 | 31 | //--------------------------------------------------------------------------------------------- 32 | // parameter settings: 33 | 34 | /** Sets the sample-rate. */ 35 | void setSampleRate(double newSampleRate); 36 | 37 | /** Sets the time constant (tau), value is expected in milliseconds. */ 38 | void setTimeConstant(double newTimeConstant); 39 | 40 | /** Sets the internal state of the filter to the passed value. */ 41 | void setState(double newState) { y1 = newState; } 42 | 43 | //--------------------------------------------------------------------------------------------- 44 | // inquiry: 45 | 46 | /** Returns the time constant (tau) in milliseconds. */ 47 | double getTimeConstant() const { return tau; } 48 | 49 | /** Returns the normalizer, required to normalize the impulse response of a series connection 50 | of two digital RC-type filters with time constants tau1 and tau2 (in milliseconds) to unity at 51 | the given samplerate. */ 52 | static double getNormalizer(double tau1, double tau2, double sampleRate); 53 | 54 | //--------------------------------------------------------------------------------------------- 55 | // audio processing: 56 | 57 | /** Calculates one sample at a time. */ 58 | INLINE double getSample(double in); 59 | 60 | //--------------------------------------------------------------------------------------------- 61 | // others: 62 | 63 | /** Resets the internal state of the filter. */ 64 | void reset(); 65 | 66 | //============================================================================================= 67 | 68 | protected: 69 | 70 | /** Calculates the filter coefficient. */ 71 | void calculateCoefficient(); 72 | 73 | double coeff; // filter coefficient 74 | double y1; // previous output sample 75 | double sampleRate; // the samplerate 76 | double tau; // time constant in milliseconds 77 | 78 | }; 79 | 80 | //----------------------------------------------------------------------------------------------- 81 | // inlined functions: 82 | 83 | INLINE double LeakyIntegrator::getSample(double in) 84 | { 85 | return y1 = in + coeff*(y1-in); 86 | } 87 | 88 | } // end namespace rosic 89 | 90 | #endif 91 | -------------------------------------------------------------------------------- /vendor/miniz/miniz.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | struct zip_archive; 8 | 9 | struct zip_info 10 | { 11 | std::string filename; 12 | 13 | struct 14 | { 15 | int year = 1980; 16 | int month = 0; 17 | int day = 0; 18 | int hours = 0; 19 | int minutes = 0; 20 | int seconds = 0; 21 | } date_time; 22 | 23 | std::string comment; 24 | std::string extra; 25 | uint16_t create_system = 0; 26 | uint16_t create_version = 0; 27 | uint16_t extract_version = 0; 28 | uint16_t flag_bits = 0; 29 | std::size_t volume = 0; 30 | uint32_t internal_attr = 0; 31 | uint32_t external_attr = 0; 32 | std::size_t header_offset = 0; 33 | uint32_t crc = 0; 34 | std::size_t compress_size = 0; 35 | std::size_t file_size = 0; 36 | }; 37 | 38 | class zip_file 39 | { 40 | public: 41 | zip_file(); 42 | zip_file(const std::string &filename); 43 | zip_file(std::istream &stream); 44 | zip_file(const std::vector &bytes); 45 | 46 | ~zip_file(); 47 | 48 | void load(std::istream &stream); 49 | void load(const std::string &filename); 50 | void load(const std::vector &bytes); 51 | 52 | void save(const std::string &filename); 53 | void save(std::ostream &stream); 54 | void save(std::vector &bytes); 55 | 56 | void reset(); 57 | 58 | bool has_file(const std::string &name); 59 | bool has_file(const zip_info &name); 60 | 61 | zip_info getinfo(const std::string &name); 62 | 63 | std::vector infolist(); 64 | 65 | std::vector namelist(); 66 | 67 | void extract(const std::string &member, const std::string &path); 68 | void extract(const zip_info &member, const std::string &path); 69 | 70 | void extractall(const std::string &path); 71 | void extractall(const std::string &path, const std::vector &members); 72 | void extractall(const std::string &path, const std::vector &members); 73 | 74 | void read(const zip_info& info, std::vector& out); 75 | std::string read(const zip_info &info); 76 | std::string read(const std::string &name); 77 | 78 | void write(const std::string &filename); 79 | void write(const std::string &filename, const std::string &arcname); 80 | void write(const std::string& arcname, std::vector const& bytes); 81 | void writestr(const std::string &arcname, const std::string &bytes); 82 | void writestr(const zip_info &info, const std::string &bytes); 83 | 84 | std::string get_filename() const; 85 | 86 | std::string comment; 87 | 88 | private: 89 | void start_read(); 90 | void start_write(); 91 | 92 | void append_comment(); 93 | void remove_comment(); 94 | 95 | zip_info getinfo(int index); 96 | 97 | std::unique_ptr archive_; 98 | std::vector buffer_; 99 | std::string filename_; 100 | }; -------------------------------------------------------------------------------- /engine/instrument/dx7/pitchenv.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #include "synth.h" 17 | #include "pitchenv.h" 18 | 19 | namespace dx7 { 20 | 21 | int PitchEnv::unit_; 22 | 23 | void PitchEnv::init(double sample_rate) { 24 | unit_ = N * (1 << 24) / (21.3 * sample_rate) + 0.5; 25 | } 26 | 27 | const uint8_t pitchenv_rate[] = { 28 | 1, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 29 | 12, 13, 13, 14, 14, 15, 16, 16, 17, 18, 18, 19, 20, 21, 22, 23, 24, 30 | 25, 26, 27, 28, 30, 31, 33, 34, 36, 37, 38, 39, 41, 42, 44, 46, 47, 31 | 49, 51, 53, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 79, 82, 32 | 85, 88, 91, 94, 98, 102, 106, 110, 115, 120, 125, 130, 135, 141, 147, 33 | 153, 159, 165, 171, 178, 185, 193, 202, 211, 232, 243, 254, 255 34 | }; 35 | 36 | const int8_t pitchenv_tab[] = { 37 | -128, -116, -104, -95, -85, -76, -68, -61, -56, -52, -49, -46, -43, 38 | -41, -39, -37, -35, -33, -32, -31, -30, -29, -28, -27, -26, -25, -24, 39 | -23, -22, -21, -20, -19, -18, -17, -16, -15, -14, -13, -12, -11, -10, 40 | -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 41 | 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 42 | 28, 29, 30, 31, 32, 33, 34, 35, 38, 40, 43, 46, 49, 53, 58, 65, 73, 43 | 82, 92, 103, 115, 127 44 | }; 45 | 46 | void PitchEnv::set(const int r[4], const int l[4]) { 47 | for (int i = 0; i < 4; i++) { 48 | rates_[i] = r[i]; 49 | levels_[i] = l[i]; 50 | } 51 | level_ = pitchenv_tab[l[3]] << 19; 52 | down_ = true; 53 | advance(0); 54 | } 55 | 56 | int32_t PitchEnv::getsample() { 57 | if (ix_ < 3 || ((ix_ < 4) && !down_)) { 58 | if (rising_) { 59 | level_ += inc_; 60 | if (level_ >= targetlevel_) { 61 | level_ = targetlevel_; 62 | advance(ix_ + 1); 63 | } 64 | } else { // !rising 65 | level_ -= inc_; 66 | if (level_ <= targetlevel_) { 67 | level_ = targetlevel_; 68 | advance(ix_ + 1); 69 | } 70 | } 71 | } 72 | return level_; 73 | } 74 | 75 | void PitchEnv::keydown(bool d) { 76 | if (down_ != d) { 77 | down_ = d; 78 | advance(d ? 0 : 3); 79 | } 80 | } 81 | 82 | void PitchEnv::advance(int newix) { 83 | ix_ = newix; 84 | if (ix_ < 4) { 85 | int newlevel = levels_[ix_]; 86 | targetlevel_ = pitchenv_tab[newlevel] << 19; 87 | rising_ = (targetlevel_ > level_); 88 | inc_ = pitchenv_rate[rates_[ix_]] * unit_; 89 | } 90 | } 91 | 92 | void PitchEnv::getPosition(char *step) { 93 | *step = ix_; 94 | } 95 | 96 | } -------------------------------------------------------------------------------- /engine/instrument/synthmachine/Value.cpp: -------------------------------------------------------------------------------- 1 | #include "Value.hpp" 2 | #include "../../core/Log.hpp" 3 | 4 | namespace sns 5 | { 6 | Value::Value(float value) 7 | :m_easing(EasingMethod::Linear) 8 | { 9 | set(value); 10 | } 11 | 12 | float Value::v() { 13 | return m_current; 14 | } 15 | 16 | void Value::setEasing(EasingMethod method) { 17 | m_easing = method; 18 | } 19 | 20 | EasingMethod Value::easingMethod() { 21 | return m_easing; 22 | } 23 | 24 | bool Value::changing() const { 25 | return m_mode != Mode::Off; 26 | } 27 | 28 | void Value::operator = (float value) { 29 | set(value); 30 | } 31 | 32 | void Value::changeWithTime(float value, uint64_t ms) { 33 | changeWithSamples(value, samplesFromMilliseconds(ms)); 34 | } 35 | 36 | void Value::changeWithSamples(float value, uint64_t samples) { 37 | if (samples <= 0) { 38 | set(value); 39 | return; 40 | } 41 | 42 | m_start = m_current; 43 | m_range = value - m_start; 44 | m_samples = (int)samples; 45 | m_current_sample = 1; 46 | m_mode = Mode::Eased; 47 | } 48 | 49 | void Value::set(float value) { 50 | m_mode = Mode::Off; 51 | m_current = value; 52 | m_start = 0.0f; 53 | m_range = 0.0f; 54 | m_samples = 0; 55 | m_current_sample = 0; 56 | m_increment = 0.0f; 57 | } 58 | 59 | void Value::changeWithIncrement(float value, float increment) { 60 | if (equivalent(m_current, value)) { 61 | set(value); 62 | return; 63 | } 64 | 65 | m_mode = Mode::Constant; 66 | 67 | m_start = m_current; 68 | m_range = value; 69 | 70 | float delta = value - m_current; 71 | float direction = (delta > 0.0f) ? 1.0f : -1.0f; 72 | m_increment = direction * absolute(increment); 73 | 74 | //Log::d("Value", sfmt("Start Value changed to %.3f inc %.3f", m_range, m_increment)); 75 | } 76 | 77 | float Value::next() { 78 | if (m_mode == Mode::Eased) { 79 | float progress = clampTo(float(m_current_sample) / float(m_samples), 0.0f, 1.0f); 80 | 81 | m_current = m_start + m_range * easing(m_easing, progress); 82 | 83 | if (m_current_sample >= m_samples) 84 | m_mode = Mode::Off; 85 | 86 | m_current_sample++; 87 | } else if (m_mode == Mode::Constant) { 88 | 89 | // m_range = target value 90 | float delta = m_range - m_current; 91 | if (absolute(delta) < absolute(m_increment)) { 92 | m_current = m_range; 93 | m_mode = Mode::Off; 94 | 95 | //Log::d("Value", sfmt("Done Value changed Done to %.3f inc %.3f", m_current, m_increment)); 96 | } else { 97 | m_current += m_increment; 98 | } 99 | 100 | } 101 | 102 | return m_current; 103 | } 104 | 105 | } -------------------------------------------------------------------------------- /engine/instrument/tb303/GlobalDefinitions.h: -------------------------------------------------------------------------------- 1 | #ifndef GlobalDefinitions_h 2 | #define GlobalDefinitions_h 3 | 4 | #include 5 | 6 | /** This file contains a bunch of useful macros which are not wrapped into the 7 | rosic namespace to facilitate their global use. */ 8 | 9 | #ifdef _MSC_VER 10 | #define INLINE __forceinline 11 | #else 12 | #define INLINE inline // something better to do here ? 13 | #endif 14 | 15 | //_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON) 16 | 17 | //------------------------------------------------------------------------------------------------- 18 | // mathematical constants: 19 | 20 | #define PI 3.1415926535897932384626433832795 21 | #define EULER 2.7182818284590452353602874713527 22 | #define SQRT2 1.4142135623730950488016887242097 23 | #define ONE_OVER_SQRT2 0.70710678118654752440084436210485 24 | #define LN10 2.3025850929940456840179914546844 25 | #define ONE_OVER_LN10 0.43429448190325182765112891891661 26 | #define LN2 0.69314718055994530941723212145818 27 | #define ONE_OVER_LN2 1.4426950408889634073599246810019 28 | #define SEMITONE_FACTOR 1.0594630943592952645618252949463 29 | 30 | //------------------------------------------------------------------------------------------------- 31 | // type definitions: 32 | 33 | // unsigned 64 bit integers: 34 | #ifdef _MSC_VER 35 | typedef unsigned __int64 UINT64; 36 | #else 37 | typedef unsigned long long UINT64; 38 | #endif 39 | 40 | // signed 64 bit integers: 41 | #ifdef _MSC_VER 42 | typedef signed __int64 INT64; 43 | #else 44 | typedef signed long long INT64; 45 | #endif 46 | 47 | // unsigned 32 bit integers: 48 | #ifdef _MSC_VER 49 | typedef unsigned __int32 UINT32; 50 | #else 51 | typedef unsigned long UINT32; 52 | #endif 53 | 54 | // ...constants for numerical precision issues, denorm, etc.: 55 | #define TINY FLT_MIN 56 | #define EPS DBL_EPSILON 57 | 58 | // define infinity values: 59 | 60 | inline double dummyFunction(double x) { return x; } 61 | #define INF (1.0/dummyFunction(0.0)) 62 | #define NEG_INF (-1.0/dummyFunction(0.0)) 63 | 64 | //------------------------------------------------------------------------------------------------- 65 | // debug stuff: 66 | 67 | // this will try to break the debugger if one is currently hosting this app: 68 | #ifdef _DEBUG 69 | 70 | #ifdef _MSC_VER 71 | #pragma intrinsic (__debugbreak) 72 | #define DEBUG_BREAK __debugbreak(); 73 | #else 74 | #define DEBUG_BREAK {} 75 | #endif 76 | 77 | #else 78 | 79 | #define DEBUG_BREAK {} // evaluate to no op in release builds 80 | 81 | #endif 82 | 83 | // an replacement of the ASSERT macro 84 | #define rassert(expression) { if (! (expression)) DEBUG_BREAK } 85 | 86 | //------------------------------------------------------------------------------------------------- 87 | // bit twiddling: 88 | 89 | //extract the exponent from a IEEE 754 floating point number (single and double precision): 90 | #define EXPOFFLT(value) (((*((reinterpret_cast(&value)))&0x7FFFFFFF)>>23)-127) 91 | #define EXPOFDBL(value) (((*((reinterpret_cast(&value)))&0x7FFFFFFFFFFFFFFFULL)>>52)-1023) 92 | // ULL indicates an unsigned long long literal constant 93 | 94 | #endif 95 | -------------------------------------------------------------------------------- /app/Configuration.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../engine/core/Lang.hpp" 4 | #include "../engine/instrument/Instrument.hpp" 5 | #include "../engine/audio/Midi.hpp" 6 | #include "../engine/Sequencer.hpp" 7 | #include "../engine/Chainer.hpp" 8 | 9 | namespace sns { 10 | 11 | constexpr char LastSessionName[] = "last session"; 12 | constexpr char DefaultSessionName[] = "default"; 13 | constexpr int ConfigurationVersion = 2; 14 | 15 | //constexpr auto PackExtension = "tar"; 16 | constexpr auto PackExtension = "zip"; 17 | 18 | class App; 19 | 20 | struct Configuration { 21 | int runs = 0; 22 | 23 | int window_width = 1024; 24 | int window_height = 768; 25 | bool window_fullscreen = false; 26 | 27 | int audio_buffer_size = 2048 / 4; 28 | int video_fps = 0; 29 | 30 | Midi::Mapping midi; 31 | std::string midi_preset; 32 | 33 | std::string project = DefaultSessionName; 34 | std::string project_folder; 35 | }; 36 | 37 | std::string rootFolder(); 38 | void saveConfiguration(App* app, Configuration const& configuration); 39 | Configuration loadConfiguration(App* app); 40 | Configuration migrateApp(App* app, Configuration cfg, int from, int to); 41 | 42 | std::vector projectNames(); 43 | void deleteProject(std::string const& name); 44 | void cloneProject(Configuration const& configuration, std::string const& name); 45 | void exportProject(Configuration const& configuration, std::string const& filename); 46 | 47 | std::vector importableProjects(std::string const& filename); 48 | bool importProject(std::string const& project, std::string const& filename); 49 | 50 | void backupProjects(); 51 | bool backupFolder(std::string const& source, std::string const& filename); 52 | 53 | // 54 | // Midi devices presets 55 | // 56 | std::vector midiPresetNames(Configuration& configuration); 57 | void loadMidiPreset(Configuration& configuration, std::string const& preset); 58 | 59 | // 60 | // Instrument presets 61 | // 62 | std::vector instrumentPresetsNames(Configuration const& configuration, InstrumentId id); 63 | ParametersValues instrumentPreset(Configuration const& configuration, InstrumentId id, std::string const& name); 64 | void saveInstrumentPreset(Configuration const& configuration, InstrumentId id, std::string name, ParametersValues const& values); 65 | void deleteInstrumentPreset(Configuration const& configuration, InstrumentId id, std::string name); 66 | 67 | // 68 | // Sequences 69 | // 70 | std::vector sequenceNames(Configuration const& configuration); 71 | Sequencer::Configuration loadSequence(Configuration const& configuration, std::string name); 72 | void saveSequence(Configuration const& configuration, std::string const& name, Sequencer::Configuration const& values); 73 | void deleteSequence(Configuration const& configuration, std::string name); 74 | 75 | // 76 | // chains 77 | // 78 | std::vector chainNames(Configuration const& configuration); 79 | Chainer::Configuration loadChain(Configuration const& configuration, std::string name); 80 | void saveChain(Configuration const& configuration, std::string const& name, Chainer::Configuration const& values); 81 | void deleteChain(Configuration const& configuration, std::string name); 82 | } -------------------------------------------------------------------------------- /app/tools/ScopeWindow.cpp: -------------------------------------------------------------------------------- 1 | #include "ScopeWindow.hpp" 2 | 3 | #include "../App.hpp" 4 | #include "../engine/core/Log.hpp" 5 | #include "../vendor/imgui/imgui.h" 6 | #include "../tools/VUMeterWindow.hpp" 7 | 8 | 9 | namespace sns { 10 | 11 | ScopeWindow::ScopeWindow() 12 | { 13 | TAG = "Scope"; 14 | m_window_name = "Scope"; 15 | 16 | m_active = true; 17 | m_offset_factor = 0.0f; 18 | 19 | m_time_values = {1, 2, 5, 10, 20, 25, 50, 100, 150, 200}; 20 | for (auto current : m_time_values) 21 | m_time_names.push_back(sfmt("%d ms", current)); 22 | m_time = 3; 23 | 24 | 25 | m_points_values = { 20, 50, 100, 150, 200 }; 26 | for (auto current : m_points_values) 27 | m_points_names.push_back(sfmt("%d", current)); 28 | m_points = 2; 29 | 30 | 31 | m_sync_values = {AnalyserSync::None, AnalyserSync::RiseZero, AnalyserSync::FallZero}; 32 | for (auto current : m_sync_values) 33 | m_sync_names.push_back(toString(current)); 34 | m_sync = 0; 35 | } 36 | 37 | ScopeWindow::~ScopeWindow() { 38 | 39 | } 40 | 41 | void ScopeWindow::render(){ 42 | beforeRender(); 43 | 44 | Analyser& analyser = app()->engine().analyser(); 45 | 46 | 47 | ImGui::Begin(m_window_name.c_str(), &m_showing, ImGuiWindowFlags_NoResize); 48 | 49 | if (m_showing && !analyser.isAccepting() && m_active) 50 | updateCapture(); 51 | 52 | float base_size = ImGui::GetTextLineHeightWithSpacing() * 2.0f; 53 | 54 | app()->engine().analyser().generateGraph(m_samples); 55 | 56 | ImGui::PlotLines("##Lines", m_samples.data(), int(m_samples.size()), 0, 57 | "", -1.0f, 1.0f, 58 | ImVec2(base_size * 10.0f, base_size * 5.0f)); 59 | 60 | ImGui::SameLine(); 61 | 62 | // 63 | // Pickers 64 | // 65 | ImGui::BeginChild("options_block", ImVec2(base_size * 4.0f, 0), true); 66 | { 67 | bool running_disabled = app()->getWindow()->showing(); 68 | 69 | if (running_disabled) 70 | ImGui::BeginDisabled(true); 71 | ImGui::Checkbox("Running", &m_active); 72 | if (running_disabled) { 73 | ImGui::EndDisabled(); 74 | ImGui::SameLine(); 75 | pHelpMarker("Disabled while VU Meter opened"); 76 | } 77 | 78 | if (pCombo("Time", m_time_names, m_time)) 79 | updateCapture(); 80 | 81 | float f = 1.0f - m_offset_factor; 82 | if (ImGui::SliderFloat("Offset", &f, 0.0f, 1.0f, "")) { 83 | m_offset_factor = 1.0f - f; 84 | updateCapture(); 85 | } 86 | 87 | if (pCombo("Sync", m_sync_names, m_sync)) 88 | updateCapture(); 89 | 90 | ImGui::Separator(); 91 | 92 | if (pCombo("Points", m_points_names, m_points)) 93 | updateCapture(); 94 | 95 | 96 | } 97 | ImGui::EndChild(); 98 | 99 | 100 | 101 | if (analyser.isAccepting() && (!m_showing || !m_active)) { 102 | analyser.stop(TAG); 103 | } 104 | 105 | aboutToFinishRender(); 106 | ImGui::End(); 107 | } 108 | 109 | void ScopeWindow::updateCapture() { 110 | Analyser& analyser = app()->engine().analyser(); 111 | 112 | analyser.configureGraph( 113 | m_points_values[m_points], 114 | m_time_values[m_time], 115 | m_offset_factor, 116 | m_sync_values[m_sync]); 117 | 118 | if (m_active) 119 | analyser.start(TAG); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /engine/audio/Easing.cpp: -------------------------------------------------------------------------------- 1 | #include "Easing.hpp" 2 | 3 | namespace sns { 4 | 5 | // 6 | // Ease 7 | // 8 | std::string toString(EasingKind kind) { 9 | switch (kind) { 10 | case EasingKind::Linear: return "Linear"; 11 | case EasingKind::Sine: return "Sine"; 12 | case EasingKind::Quad: return "Quad"; 13 | case EasingKind::Cubic: return "Cubic"; 14 | case EasingKind::Quart: return "Quart"; 15 | case EasingKind::Quint: return "Quint"; 16 | case EasingKind::Expo: return "Expo"; 17 | default: break; 18 | } 19 | return "[EasingKind NOT_SET]"; 20 | } 21 | 22 | std::string toString(EasingMethod method) { 23 | switch (method) { 24 | case EasingMethod::Linear: return "Linear"; 25 | 26 | case EasingMethod::InSine: return "InSine"; 27 | case EasingMethod::OutSine: return "OutSine"; 28 | 29 | case EasingMethod::InQuad: return "InQuad"; 30 | case EasingMethod::OutQuad: return "OutQuad"; 31 | 32 | case EasingMethod::InCubic: return "InCubic"; 33 | case EasingMethod::OutCubic: return "OutCubic"; 34 | 35 | case EasingMethod::InQuart: return "InQuart"; 36 | case EasingMethod::OutQuart: return "OutQuart"; 37 | 38 | case EasingMethod::InQuint: return "InQuint"; 39 | case EasingMethod::OutQuint: return "OutQuint"; 40 | 41 | case EasingMethod::InExpo: return "InExpo"; 42 | case EasingMethod::OutExpo: return "OutExpo"; 43 | default: break; 44 | } 45 | return "[EasingMethod NOT_SET]"; 46 | } 47 | 48 | float easing(EasingMethod method, float x) { 49 | switch (method) { 50 | case EasingMethod::InSine: return easeInSine(x); 51 | case EasingMethod::OutSine: return easeOutSine(x); 52 | 53 | case EasingMethod::InQuad: return easeInQuad(x); 54 | case EasingMethod::OutQuad: return easeOutQuad(x); 55 | 56 | case EasingMethod::InCubic: return easeInCubic(x); 57 | case EasingMethod::OutCubic: return easeOutCubic(x); 58 | 59 | case EasingMethod::InQuart: return easeInQuart(x); 60 | case EasingMethod::OutQuart: return easeOutQuart(x); 61 | 62 | case EasingMethod::InQuint: return easeInQuint(x); 63 | case EasingMethod::OutQuint: return easeOutQuint(x); 64 | 65 | case EasingMethod::InExpo: return easeInExpo(x); 66 | case EasingMethod::OutExpo: return easeOutExpo(x); 67 | default: break; 68 | } 69 | return x; 70 | } 71 | 72 | std::vector easingNames() { 73 | return enumNames(); 74 | } 75 | 76 | std::vector easingKindNames() { 77 | return enumNames(); 78 | } 79 | 80 | std::pair easingPair(EasingKind kind) { 81 | switch (kind) { 82 | case EasingKind::Sine: return std::make_pair<>(EasingMethod::InSine, EasingMethod::OutSine); 83 | case EasingKind::Quad: return std::make_pair<>(EasingMethod::InQuad, EasingMethod::OutQuad); 84 | case EasingKind::Cubic: return std::make_pair<>(EasingMethod::InCubic, EasingMethod::OutCubic); 85 | case EasingKind::Quart: return std::make_pair<>(EasingMethod::InQuart, EasingMethod::OutQuart); 86 | case EasingKind::Quint: return std::make_pair<>(EasingMethod::InQuint, EasingMethod::OutQuint); 87 | case EasingKind::Expo: return std::make_pair<>(EasingMethod::InExpo, EasingMethod::OutExpo); 88 | default: break; 89 | } 90 | return std::make_pair<>(EasingMethod::Linear, EasingMethod::Linear); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /engine/instrument/dx7/controllers.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* 3 | * Copyright 2013 Google Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | #include "synth.h" 18 | 19 | namespace dx7 { 20 | 21 | // State of MIDI controllers 22 | const int kControllerPitch = 128; 23 | const int kControllerPitchRangeUp = 129; 24 | const int kControllerPitchStep = 130; 25 | const int kControllerPitchRangeDn = 131; 26 | 27 | class FmCore; 28 | 29 | struct FmMod { 30 | int range; 31 | bool pitch; 32 | bool amp; 33 | bool eg; 34 | 35 | FmMod() { 36 | range = 0; 37 | pitch = false; 38 | amp = false; 39 | eg = false; 40 | } 41 | 42 | void parseConfig(const char *cfg) { 43 | int r = 0, p = 0, a = 0, e = 0; 44 | sscanf(cfg, "%d %d %d %d", &r, &p, &a, &e); 45 | 46 | range = r < 0 || r > 127 ? 0 : r; 47 | pitch = p != 0; 48 | amp = a != 0; 49 | eg = e != 0; 50 | } 51 | 52 | void setConfig(char *cfg) { 53 | snprintf(cfg, 13, "%d %d %d %d", range, pitch, amp, eg); 54 | } 55 | }; 56 | 57 | class Controllers { 58 | void applyMod(int cc, FmMod &mod) { 59 | float range = 0.01 * mod.range; 60 | int total = cc * range; 61 | if ( mod.amp ) 62 | amp_mod = std::max(amp_mod, total); 63 | 64 | if ( mod.pitch ) 65 | pitch_mod = std::max(pitch_mod, total); 66 | 67 | if ( mod.eg ) 68 | eg_mod = std::max(eg_mod, total); 69 | } 70 | 71 | public: 72 | int values_[132]; 73 | 74 | char opSwitch[7]; 75 | 76 | int amp_mod; 77 | int pitch_mod; 78 | int eg_mod; 79 | 80 | int aftertouch_cc; 81 | int breath_cc; 82 | int foot_cc; 83 | int modwheel_cc; 84 | 85 | int masterTune; 86 | 87 | bool transpose12AsScale = true; 88 | 89 | // MPE configuration. FIXME - make this switchable 90 | bool mpeEnabled = true; 91 | int mpePitchBendRange = 24; 92 | 93 | FmMod wheel; 94 | FmMod foot; 95 | FmMod breath; 96 | FmMod at; 97 | 98 | Controllers() { 99 | amp_mod = 0; 100 | pitch_mod = 0; 101 | eg_mod = 0; 102 | strcpy(opSwitch, "111111"); 103 | } 104 | 105 | void refresh() { 106 | amp_mod = 0; 107 | pitch_mod = 0; 108 | eg_mod = 0; 109 | 110 | applyMod(modwheel_cc, wheel); 111 | applyMod(breath_cc, breath); 112 | applyMod(foot_cc, foot); 113 | applyMod(aftertouch_cc, at); 114 | 115 | if ( ! ((wheel.eg || foot.eg) || (breath.eg || at.eg)) ) 116 | eg_mod = 127; 117 | 118 | } 119 | 120 | FmCore *core; 121 | }; 122 | 123 | } 124 | -------------------------------------------------------------------------------- /engine/instrument/synthmachine/Envelope.cpp: -------------------------------------------------------------------------------- 1 | #include "Envelope.hpp" 2 | 3 | #include "../../core/Log.hpp" 4 | 5 | namespace sns { 6 | constexpr float HYSTERESIS = 0.0001f; // -80dB 7 | constexpr float MIN_TIME_MS = 10.0f; 8 | 9 | //////////////////////////////////////////////////////////////////////////// 10 | // fast exp Taylor series approximations (from http://www.musicdsp.org/) 11 | //////////////////////////////////////////////////////////////////////////// 12 | constexpr float fast_exp3(float x) { 13 | return (6 + x * (6 + x * (3 + x))) * 0.16666666f; 14 | } 15 | 16 | constexpr float calculateRate(float factor, float max_time) { 17 | const float ms = maximum(factor * max_time, MIN_TIME_MS); 18 | const float samples = (ms / 1000.0f) * float(SampleRate); 19 | return fast_exp3(-2.0f / samples); 20 | } 21 | 22 | 23 | Envelope::Envelope() 24 | :m_stage(Stage::NotStarted), 25 | m_current(0.0f), 26 | m_attack_rate(0.0f), 27 | m_decay_rate(0.0f), 28 | m_sustain(1.0f), 29 | m_release_rate(0.0f), 30 | m_off_level(0.0f), 31 | m_level(0.0f), 32 | m_kill_rate(calculateRate(0, MIN_TIME_MS)) 33 | { 34 | } 35 | 36 | void Envelope::setAttack(float a) { m_attack_rate = calculateRate(a, 2000.0f); } 37 | void Envelope::setDecay(float d) { m_decay_rate = calculateRate(d, 2000.0f); } 38 | void Envelope::setSustain(float s) { m_sustain = s; } 39 | void Envelope::setRelease(float r) { m_release_rate = calculateRate(r, 2000.0f); } 40 | 41 | void Envelope::trigger(float level) { 42 | if (m_current <= level) { 43 | m_level = level; 44 | m_stage = Stage::Attack; 45 | } 46 | } 47 | 48 | void Envelope::release() { 49 | if (m_stage == Stage::NotStarted) 50 | m_stage = Stage::Off; 51 | else if (m_stage < Stage::Release) 52 | m_stage = Stage::Release; 53 | } 54 | 55 | void Envelope::kill() { 56 | if ((m_stage == Stage::NotStarted) || (m_stage == Stage::Off)) 57 | m_stage = Stage::Off; 58 | else 59 | m_stage = Stage::Kill; 60 | } 61 | 62 | bool Envelope::completed() { 63 | return (m_stage == Stage::Off); 64 | } 65 | 66 | void Envelope::updateAttack() { 67 | m_current = 1.6f + m_attack_rate * (m_current - 1.6f); 68 | if (m_current > m_level) { 69 | m_current = m_level; 70 | 71 | m_stage = Stage::Decay; 72 | if (equivalent(1.0f, m_sustain)) 73 | m_stage = Stage::Sustain; 74 | } 75 | } 76 | 77 | void Envelope::updateDecay() { 78 | auto level = m_level * m_sustain; 79 | m_current = level + m_decay_rate * (m_current - level); 80 | if (m_current < (level + HYSTERESIS)) { 81 | m_current = level; 82 | m_stage = Stage::Sustain; 83 | } 84 | } 85 | 86 | void Envelope::updateRelease(float rate) { 87 | m_current = m_off_level + rate * (m_current - m_off_level); 88 | if (m_current < (m_off_level + HYSTERESIS)) { 89 | m_current = m_off_level; 90 | m_stage = Stage::Off; 91 | } 92 | } 93 | 94 | float Envelope::next() { 95 | 96 | switch (m_stage) { 97 | case Stage::Attack: updateAttack(); break; 98 | case Stage::Decay: updateDecay(); break; 99 | case Stage::Release: updateRelease(m_release_rate); break; 100 | case Stage::Kill: updateRelease(m_kill_rate); break; 101 | default: break; 102 | } 103 | 104 | return m_current; 105 | } 106 | } -------------------------------------------------------------------------------- /engine/instrument/tb303/rosic_TeeBeeFilter.cpp: -------------------------------------------------------------------------------- 1 | #include "rosic_TeeBeeFilter.h" 2 | using namespace rosic; 3 | 4 | //------------------------------------------------------------------------------------------------- 5 | // construction/destruction: 6 | 7 | TeeBeeFilter::TeeBeeFilter() 8 | { 9 | cutoff = 1000.0; 10 | drive = 0.0; 11 | driveFactor = 1.0; 12 | resonanceRaw = 0.0; 13 | resonanceSkewed = 0.0; 14 | g = 1.0; 15 | sampleRate = 44100.0; 16 | twoPiOverSampleRate = 2.0*PI/sampleRate; 17 | 18 | feedbackHighpass.setMode(OnePoleFilter::HIGHPASS); 19 | feedbackHighpass.setCutoff(150.0); 20 | 21 | //setMode(LP_18); 22 | setMode(TB_303); 23 | calculateCoefficientsExact(); 24 | reset(); 25 | } 26 | 27 | TeeBeeFilter::~TeeBeeFilter() 28 | { 29 | 30 | } 31 | 32 | //------------------------------------------------------------------------------------------------- 33 | // parameter settings: 34 | 35 | void TeeBeeFilter::setSampleRate(double newSampleRate) 36 | { 37 | if( newSampleRate > 0.0 ) 38 | sampleRate = newSampleRate; 39 | twoPiOverSampleRate = 2.0*PI/sampleRate; 40 | feedbackHighpass.setSampleRate(newSampleRate); 41 | calculateCoefficientsExact(); 42 | } 43 | 44 | void TeeBeeFilter::setDrive(double newDrive) 45 | { 46 | drive = newDrive; 47 | driveFactor = dB2amp(drive); 48 | } 49 | 50 | void TeeBeeFilter::setMode(int newMode) 51 | { 52 | if( newMode >= 0 && newMode < NUM_MODES ) 53 | { 54 | mode = newMode; 55 | switch(mode) 56 | { 57 | case FLAT: c0 = 1.0; c1 = 0.0; c2 = 0.0; c3 = 0.0; c4 = 0.0; break; 58 | case LP_6: c0 = 0.0; c1 = 1.0; c2 = 0.0; c3 = 0.0; c4 = 0.0; break; 59 | case LP_12: c0 = 0.0; c1 = 0.0; c2 = 1.0; c3 = 0.0; c4 = 0.0; break; 60 | case LP_18: c0 = 0.0; c1 = 0.0; c2 = 0.0; c3 = 1.0; c4 = 0.0; break; 61 | case LP_24: c0 = 0.0; c1 = 0.0; c2 = 0.0; c3 = 0.0; c4 = 1.0; break; 62 | case HP_6: c0 = 1.0; c1 = -1.0; c2 = 0.0; c3 = 0.0; c4 = 0.0; break; 63 | case HP_12: c0 = 1.0; c1 = -2.0; c2 = 1.0; c3 = 0.0; c4 = 0.0; break; 64 | case HP_18: c0 = 1.0; c1 = -3.0; c2 = 3.0; c3 = -1.0; c4 = 0.0; break; 65 | case HP_24: c0 = 1.0; c1 = -4.0; c2 = 6.0; c3 = -4.0; c4 = 1.0; break; 66 | case BP_12_12: c0 = 0.0; c1 = 0.0; c2 = 1.0; c3 = -2.0; c4 = 1.0; break; 67 | case BP_6_18: c0 = 0.0; c1 = 0.0; c2 = 0.0; c3 = 1.0; c4 = -1.0; break; 68 | case BP_18_6: c0 = 0.0; c1 = 1.0; c2 = -3.0; c3 = 3.0; c4 = -1.0; break; 69 | case BP_6_12: c0 = 0.0; c1 = 0.0; c2 = 1.0; c3 = -1.0; c4 = 0.0; break; 70 | case BP_12_6: c0 = 0.0; c1 = 1.0; c2 = -2.0; c3 = 1.0; c4 = 0.0; break; 71 | case BP_6_6: c0 = 0.0; c1 = 1.0; c2 = -1.0; c3 = 0.0; c4 = 0.0; break; 72 | default: c0 = 1.0; c1 = 0.0; c2 = 0.0; c3 = 0.0; c4 = 0.0; // flat 73 | } 74 | } 75 | calculateCoefficientsApprox4(); 76 | } 77 | 78 | //------------------------------------------------------------------------------------------------- 79 | // others: 80 | 81 | void TeeBeeFilter::reset() 82 | { 83 | feedbackHighpass.reset(); 84 | y1 = 0.0; 85 | y2 = 0.0; 86 | y3 = 0.0; 87 | y4 = 0.0; 88 | } 89 | -------------------------------------------------------------------------------- /engine/instrument/dx7/banks/Banks.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace dx7 { 8 | 9 | class Banks { 10 | public: 11 | 12 | struct BankInfo { 13 | std::string group; 14 | std::string bank; 15 | int size = 0; 16 | const uint8_t* data = 0; 17 | 18 | std::vector patches; 19 | }; 20 | 21 | static void generateFilesFromFolder(std::string folder); 22 | 23 | static Banks& instance(); 24 | 25 | std::vector const& groups() const; 26 | std::vector banks(int group_index) const; 27 | std::vector patches(int group_index, int bank_index) const; 28 | BankInfo bank(int group_index, int bank_index) const; 29 | private: 30 | Banks(); 31 | ~Banks(); 32 | 33 | std::vector m_banks; 34 | std::vector m_groups; 35 | std::map> m_group_bank_names; 36 | std::map m_bank_map; 37 | 38 | void buildRecords(); 39 | void buildDatabase(); 40 | 41 | // 42 | // Factory 43 | // 44 | static const uint8_t FactoryKeyboardPluckedRom1bData[]; 45 | static const uint8_t FactoryKeyboardPluckedRom3bData[]; 46 | static const uint8_t FactoryMasterEuJpRom1aData[]; 47 | static const uint8_t FactoryMasterUsRom3aData[]; 48 | static const uint8_t FactoryOrchestralPercussiveRom2aData[]; 49 | static const uint8_t FactoryOrchestralPercussiveRom4aData[]; 50 | static const uint8_t FactorySynthComplexEffectsRom2bData[]; 51 | static const uint8_t FactorySynthComplexEffectsRom4bData[]; 52 | 53 | // 54 | // VRC 55 | // 56 | static const uint8_t VrcBoTomlynSelectionIiVrc111aData[]; 57 | static const uint8_t VrcBoTomlynSelectionIiVrc111bData[]; 58 | static const uint8_t VrcBoTomlynSelectionVrc110aData[]; 59 | static const uint8_t VrcBoTomlynSelectionVrc110bData[]; 60 | static const uint8_t VrcDavidBristowSelectionVrc107aData[]; 61 | static const uint8_t VrcDavidBristowSelectionVrc107bData[]; 62 | static const uint8_t VrcGaryLeuenbergerSelectionVrc108aData[]; 63 | static const uint8_t VrcGaryLeuenbergerSelectionVrc108bData[]; 64 | static const uint8_t VrcKeyboardPluckedTunedPercVrc101aData[]; 65 | static const uint8_t VrcKeyboardPluckedTunedPercVrc101bData[]; 66 | static const uint8_t VrcLive64AkiraInoueVrc112aData[]; 67 | static const uint8_t VrcLive64AkiraInoueVrc112bData[]; 68 | static const uint8_t VrcPercussionVrc104aData[]; 69 | static const uint8_t VrcPercussionVrc104bData[]; 70 | static const uint8_t VrcSoundEffectVrc105aData[]; 71 | static const uint8_t VrcSoundEffectVrc105bData[]; 72 | static const uint8_t VrcStudio64Vrc109aData[]; 73 | static const uint8_t VrcStudio64Vrc109bData[]; 74 | static const uint8_t VrcSustainVrc103aData[]; 75 | static const uint8_t VrcSustainVrc103bData[]; 76 | static const uint8_t VrcSynthesizerVrc106aData[]; 77 | static const uint8_t VrcSynthesizerVrc106bData[]; 78 | static const uint8_t VrcWindInstrumentVrc102aData[]; 79 | static const uint8_t VrcWindInstrumentVrc102bData[]; 80 | 81 | // 82 | // E Grey 83 | // 84 | static const uint8_t EGreyMatterDisk2Data[]; 85 | static const uint8_t EGreyMatterDisk5Data[]; 86 | static const uint8_t EGreyMatterDisk7Data[]; 87 | 88 | // 89 | // Finetales 90 | // 91 | static const uint8_t FinetalesEpsAndBellsData[]; 92 | static const uint8_t FinetalesPlucksBassesBrassData[]; 93 | static const uint8_t FinetalesStringsPadsEtcData[]; 94 | 95 | }; 96 | 97 | } -------------------------------------------------------------------------------- /engine/instrument/tb303/rosic_DecayEnvelope.h: -------------------------------------------------------------------------------- 1 | #ifndef rosic_DecayEnvelope_h 2 | #define rosic_DecayEnvelope_h 3 | 4 | // rosic-indcludes: 5 | #include "rosic_RealFunctions.h" 6 | 7 | namespace rosic 8 | { 9 | 10 | /** 11 | 12 | This is a class implements an envelope generator that realizes a pure exponential decay. The 13 | output of the envelope is normalized to the range 0...1. 14 | 15 | */ 16 | 17 | class DecayEnvelope 18 | { 19 | 20 | public: 21 | 22 | //--------------------------------------------------------------------------------------------- 23 | // construction/destruction: 24 | 25 | /** Constructor. */ 26 | DecayEnvelope(); 27 | 28 | /** Destructor. */ 29 | ~DecayEnvelope(); 30 | 31 | //--------------------------------------------------------------------------------------------- 32 | // parameter settings: 33 | 34 | /** Sets the sample-rate. */ 35 | void setSampleRate(double newSampleRate); 36 | 37 | /** Sets the time constant for the multiplicative accumulator (which we consider as primarily 38 | responsible for the decaying part) in milliseconds. */ 39 | void setDecayTimeConstant(double newTimeConstant); 40 | 41 | /** Switches into a mode where the normalization is not made with respect to the peak amplitude 42 | but to the sum of the impulse response - if true, the output will be equivalent to a leaky 43 | integrator's impulse response. */ 44 | void setNormalizeSum(bool shouldNormalizeSum); 45 | 46 | //--------------------------------------------------------------------------------------------- 47 | // inquiry: 48 | 49 | /** Returns the length of the decay phase (in milliseconds). */ 50 | double getDecayTimeConstant() const { return tau; } 51 | 52 | /** True, if output is below some threshold. */ 53 | bool endIsReached(double threshold); 54 | 55 | //--------------------------------------------------------------------------------------------- 56 | // audio processing: 57 | 58 | /** Calculates one output sample at a time. */ 59 | INLINE double getSample(); 60 | 61 | //--------------------------------------------------------------------------------------------- 62 | // others: 63 | 64 | /** Triggers the envelope - the next sample retrieved via getSample() will be 1. */ 65 | void trigger(); 66 | 67 | protected: 68 | 69 | /** Calculates the coefficient for multiplicative accumulation. */ 70 | void calculateCoefficient(); 71 | 72 | double c; // coefficient for multiplicative accumulation 73 | double y; // previous output 74 | double yInit; // initial yalue for previous output (= y/c) 75 | double tau; // time-constant (in milliseconds) 76 | double fs; // sample-rate 77 | bool normalizeSum; // flag to indicate that the output should be normalized such that the 78 | // sum of the impulse response is unity (instead of the peak) - if true 79 | // the output will be equivalent to a leaky integrator's impulse 80 | // response 81 | 82 | }; 83 | 84 | //----------------------------------------------------------------------------------------------- 85 | // inlined functions: 86 | 87 | INLINE double DecayEnvelope::getSample() 88 | { 89 | y *= c; 90 | return y; 91 | } 92 | 93 | } // end namespace rosic 94 | 95 | #endif // rosic_DecayEnvelope_h 96 | -------------------------------------------------------------------------------- /app/Window.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../engine/Engine.hpp" 4 | #include "json.hpp" 5 | 6 | namespace sns { 7 | class App; 8 | 9 | 10 | class Window { 11 | public: 12 | enum class Anchor { 13 | Start, 14 | Center, 15 | End, 16 | }; 17 | 18 | Window(); 19 | virtual ~Window(); 20 | 21 | int subtype() const; 22 | std::string const& name(); 23 | 24 | void setApp(App* app); 25 | App* app(); 26 | 27 | 28 | virtual void show(bool value); 29 | bool showing() const; 30 | int zOrder() const; 31 | void setPosition(int x, int y); 32 | void anchorTo(Anchor x = Anchor::Center, Anchor y = Anchor::Center); 33 | 34 | bool isInstrumentWindow() const; 35 | 36 | virtual void initialize(); 37 | virtual void shutdown(); 38 | virtual void render(); 39 | 40 | void saveTo(nlohmann::json& json); 41 | void loadFrom(nlohmann::json& json); 42 | 43 | void setValues(ParametersValues values); 44 | 45 | 46 | static void uiNamePicker(bool trigger, 47 | std::string const& title, std::string const& item, std::string const& hint, 48 | std::function callback); 49 | 50 | //NonCopyable 51 | Window(Window const&) = delete; 52 | Window(Window const&&) = delete; 53 | Window& operator=(Window const&) = delete; 54 | Window& operator=(Window const&&) = delete; 55 | protected: 56 | std::string TAG; 57 | App* m_app; 58 | bool m_showing; 59 | int m_position_x; 60 | int m_position_y; 61 | bool m_position_changed; 62 | Anchor m_anchor_x; 63 | Anchor m_anchor_y; 64 | bool m_anchor_changed; 65 | int m_z_order; 66 | bool m_collapsed; 67 | std::string m_window_name; 68 | int m_subtype; 69 | bool m_is_instrument; 70 | bool m_has_presets; 71 | 72 | void beforeRender(); 73 | void aboutToFinishRender(); 74 | 75 | // 76 | // values and presets 77 | // 78 | ParametersValues m_values; 79 | std::string m_last_preset_selected; 80 | std::vector m_preset_names; 81 | void savePreset(std::string const& name); 82 | void loadPreset(std::string const& name); 83 | void deletePreset(std::string const& name); 84 | 85 | virtual void onSavePreset(std::string const& name); 86 | virtual void onLoadPreset(std::string const& name); 87 | virtual void onDeletePreset(std::string const& name); 88 | virtual void onRefreshPresets(); 89 | 90 | void applyInstrumentValues(); 91 | 92 | void renderPresets(float height, std::string const& title = "PRESETS", std::string const& item_singular = "preset"); 93 | 94 | static void renderSaveList(float height, 95 | std::vector const& names, std::string const& last_selected, 96 | std::string const& title, std::string const& item_singular, 97 | std::function load_callback, 98 | std::function save_callback, 99 | std::function delete_callback); 100 | 101 | // 102 | // render helpers 103 | // 104 | void pKnob(std::string const& name, Parameter param, float min = 0.0f, float max = 100.0f); 105 | void pCombo(std::string const& name, Parameter param, std::vector const& values); 106 | bool pCombo(std::string const& name, std::vector const& values, int& index); 107 | 108 | void pBool(Parameter param, std::string name); 109 | 110 | void pTableCheck(Parameter param, float spacing = 0.0f); 111 | void pTableDragInt(Parameter param, int min = 0, int max = 99); 112 | 113 | void pHelpMarker(std::string const& name); 114 | }; 115 | } -------------------------------------------------------------------------------- /engine/audio/Audio.cpp: -------------------------------------------------------------------------------- 1 | #include "Audio.hpp" 2 | #include "../core/Text.hpp" 3 | 4 | namespace sns { 5 | 6 | std::string timecode(uint64_t milliseconds) { 7 | uint64_t ms = milliseconds % uint64_t(1000); 8 | uint64_t seconds = (milliseconds / uint64_t(1000)) % uint64_t(60); 9 | uint64_t minutes = (milliseconds / uint64_t(60 * 1000)) % uint64_t(60); 10 | uint64_t hours = (milliseconds / uint64_t(60 * 60 * 1000)); 11 | return sfmt("%02d:%02d:%02d.%03d", hours, minutes, seconds, ms); 12 | } 13 | 14 | constexpr static float MIN_DB = -30.0f;//-96.f; 15 | 16 | float toDb(float linear) { 17 | return linear > 0.f ? 20.f * std::log10(linear) : MIN_DB; 18 | } 19 | 20 | float fromDb(float db) { 21 | return db <= MIN_DB ? 0.f : std::pow(10.f, db / 20.f); 22 | } 23 | 24 | float toCent(float factor) { 25 | return std::log(factor) / log(2.f) * 1200.f; 26 | } 27 | 28 | float fromCent(float cent) { 29 | return std::pow(2.f, cent / 1200.f); 30 | } 31 | 32 | float toSemitone(float factor) { 33 | return std::log(factor) / log(2.f) * 12.f; 34 | } 35 | 36 | float fromSemitone(float semitone) { 37 | return std::pow(2.f, semitone / 12.f); 38 | } 39 | 40 | float linearToLinear(float in, float in_min, float in_max, float out_min, float out_max) { 41 | float v = (in - in_min) / (in_max - in_min); 42 | return out_min + v * (out_max - out_min); 43 | } 44 | 45 | float linearToExponential(float in, float in_min, float in_max, float out_min, float out_max) { 46 | float v = (in - in_min) / (in_max - in_min); 47 | return out_min * exp(v * log(out_max / out_min)); 48 | } 49 | 50 | 51 | static std::string noteName(int note, bool octave, std::string const names[12]) { 52 | if (octave) { 53 | int note_octave = noteOctave(note); 54 | return sfmt("%s%d", names[note % 12], note_octave); 55 | } 56 | 57 | return names[note % 12]; 58 | } 59 | 60 | std::string noteName(int note, bool octave, bool alternate) { 61 | static const std::string n0[12] = { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" }; 62 | static const std::string n1[12] = { "C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B" }; 63 | 64 | return noteName(note, octave, alternate ? n1 : n0); 65 | } 66 | 67 | int noteIndex(int note, int octave) { 68 | assert(octave >= -1 && octave < (TotalOctaves - 1)); 69 | return note + (octave + 1) * 12; 70 | } 71 | 72 | int noteOctave(int note) { 73 | return (note / 12) - 1; 74 | } 75 | 76 | std::array noteFrequencies() { 77 | std::array frequencies = {}; 78 | for (size_t note = 0; note != TotalNotes; ++note) { 79 | 80 | const int delta = int(note) - 69; 81 | const double frequency = 440.0 * fromSemitone(float(delta)); 82 | 83 | frequencies[note] = float(frequency); 84 | } 85 | return frequencies; 86 | } 87 | 88 | float noteFrequency(int note) { 89 | assert(note >= 0); 90 | assert(note < TotalNotes); 91 | 92 | static std::array frequencies = noteFrequencies(); 93 | 94 | return frequencies[note]; 95 | } 96 | 97 | 98 | // 99 | // Clipping 100 | // 101 | 102 | float SoftLimit(float x) { 103 | return x * (27.f + x * x) / (27.f + 9.f * x * x); 104 | } 105 | 106 | float SoftClip(float x) { 107 | if (x < -3.0f) 108 | return -1.0f; 109 | else if (x > 3.0f) 110 | return 1.0f; 111 | else 112 | return SoftLimit(x); 113 | } 114 | 115 | float HardClip(float x) { 116 | return maximum(minimum(x, 1.0f), -1.0f); 117 | } 118 | 119 | } -------------------------------------------------------------------------------- /engine/instrument/tb303/rosic_BiquadFilter.h: -------------------------------------------------------------------------------- 1 | #ifndef rosic_BiquadFilter_h 2 | #define rosic_BiquadFilter_h 3 | 4 | // rosic-indcludes: 5 | #include "rosic_RealFunctions.h" 6 | 7 | namespace rosic 8 | { 9 | 10 | /** 11 | 12 | This is an implementation of a simple one-pole filter unit. 13 | 14 | */ 15 | 16 | class BiquadFilter 17 | { 18 | 19 | public: 20 | 21 | /** Enumeration of the available filter modes. */ 22 | enum modes 23 | { 24 | BYPASS = 0, 25 | LOWPASS6, 26 | LOWPASS12, 27 | HIGHPASS6, 28 | HIGHPASS12, 29 | BANDPASS, 30 | BANDREJECT, 31 | PEAK, 32 | LOW_SHELF, 33 | //HIGH_SHELF, 34 | //ALLPASS, 35 | 36 | NUM_FILTER_MODES 37 | }; 38 | 39 | //--------------------------------------------------------------------------------------------- 40 | // construction/destruction: 41 | 42 | /** Constructor. */ 43 | BiquadFilter(); 44 | 45 | //--------------------------------------------------------------------------------------------- 46 | // parameter settings: 47 | 48 | /** Sets the sample-rate (in Hz) at which the filter runs. */ 49 | void setSampleRate(double newSampleRate); 50 | 51 | /** Sets the filter mode as one of the values in enum modes. */ 52 | void setMode(int newMode); 53 | 54 | /** Sets the center frequency in Hz. */ 55 | void setFrequency(double newFrequency); 56 | 57 | /** Sets the boost/cut gain in dB. */ 58 | void setGain(double newGain); 59 | 60 | /** Sets the bandwidth in octaves. */ 61 | void setBandwidth(double newBandwidth); 62 | 63 | //--------------------------------------------------------------------------------------------- 64 | // inquiry 65 | 66 | /** Sets the filter mode as one of the values in enum modes. */ 67 | int getMode() const { return mode; } 68 | 69 | /** Returns the center frequency in Hz. */ 70 | double getFrequency() const { return frequency; } 71 | 72 | /** Returns the boost/cut gain in dB. */ 73 | double getGain() const { return gain; } 74 | 75 | /** Returns the bandwidth in octaves. */ 76 | double getBandwidth() const { return bandwidth; } 77 | 78 | //--------------------------------------------------------------------------------------------- 79 | // audio processing: 80 | 81 | /** Calculates a single filtered output-sample. */ 82 | INLINE double getSample(double in); 83 | 84 | //--------------------------------------------------------------------------------------------- 85 | // others: 86 | 87 | /** Resets the internal buffers (for the \f$ x[n-1], y[n-1] \f$-samples) to zero. */ 88 | void reset(); 89 | 90 | //============================================================================================= 91 | 92 | protected: 93 | 94 | // internal functions: 95 | void calcCoeffs(); // calculates filter coefficients from filter parameters 96 | 97 | double b0, b1, b2, a1, a2; 98 | double x1, x2, y1, y2; 99 | 100 | double frequency, gain, bandwidth; 101 | double sampleRate; 102 | int mode; 103 | 104 | }; 105 | 106 | //----------------------------------------------------------------------------------------------- 107 | // inlined functions: 108 | 109 | INLINE double BiquadFilter::getSample(double in) 110 | { 111 | // calculate the output sample: 112 | double y = b0*in + b1*x1 + b2*x2 + a1*y1 + a2*y2 + TINY; 113 | 114 | // update the buffer variables: 115 | x2 = x1; 116 | x1 = in; 117 | y2 = y1; 118 | y1 = y; 119 | 120 | return y; 121 | } 122 | 123 | } // end namespace rosic 124 | 125 | #endif // rosic_BiquadFilter_h 126 | -------------------------------------------------------------------------------- /engine/instrument/tb303/rosic_EllipticQuarterBandFilter.h: -------------------------------------------------------------------------------- 1 | #ifndef rosic_EllipticQuarterBandFilter_h 2 | #define rosic_EllipticQuarterBandFilter_h 3 | 4 | #include // for memmove 5 | 6 | // rosic-indcludes: 7 | #include "GlobalDefinitions.h" 8 | 9 | namespace rosic 10 | { 11 | 12 | /** 13 | 14 | This is an elliptic subband filter of 12th order using a Direct Form II implementation structure. 15 | 16 | */ 17 | 18 | class EllipticQuarterBandFilter 19 | { 20 | 21 | public: 22 | 23 | //--------------------------------------------------------------------------------------------- 24 | // construction/destruction: 25 | 26 | /** Constructor. */ 27 | EllipticQuarterBandFilter(); 28 | 29 | //--------------------------------------------------------------------------------------------- 30 | // parameter settings: 31 | 32 | /** Resets the filter state. */ 33 | void reset(); 34 | 35 | //--------------------------------------------------------------------------------------------- 36 | // audio processing: 37 | 38 | /** Calculates a single filtered output-sample. */ 39 | INLINE double getSample(double in); 40 | 41 | //============================================================================================= 42 | 43 | protected: 44 | 45 | // state buffer: 46 | double w[12]; 47 | 48 | }; 49 | 50 | //----------------------------------------------------------------------------------------------- 51 | // inlined functions: 52 | 53 | INLINE double EllipticQuarterBandFilter::getSample(double in) 54 | { 55 | const double a01 = -9.1891604652189471; 56 | const double a02 = 40.177553696870497; 57 | const double a03 = -110.11636661771178; 58 | const double a04 = 210.18506612078195; 59 | const double a05 = -293.84744771903240; 60 | const double a06 = 308.16345558359234; 61 | const double a07 = -244.06786780384243; 62 | const double a08 = 144.81877911392738; 63 | const double a09 = -62.770692151724198; 64 | const double a10 = 18.867762095902137; 65 | const double a11 = -3.5327094230551848; 66 | const double a12 = 0.31183189275203149; 67 | 68 | const double b00 = 0.00013671732099945628; 69 | const double b01 = -0.00055538501265606384; 70 | const double b02 = 0.0013681887636296387; 71 | const double b03 = -0.0022158566490711852; 72 | const double b04 = 0.0028320091007278322; 73 | const double b05 = -0.0029776933151090413; 74 | const double b06 = 0.0030283628243514991; 75 | const double b07 = -0.0029776933151090413; 76 | const double b08 = 0.0028320091007278331; 77 | const double b09 = -0.0022158566490711861; 78 | const double b10 = 0.0013681887636296393; 79 | const double b11 = -0.00055538501265606384; 80 | const double b12 = 0.00013671732099945636; 81 | 82 | // calculate intermediate and output sample via direct form II - the parentheses facilitate 83 | // out-of-order execution of the independent additions (for performance optimization): 84 | double tmp = (in + TINY) 85 | - ( (a01*w[0] + a02*w[1] ) + (a03*w[2] + a04*w[3] ) ) 86 | - ( (a05*w[4] + a06*w[5] ) + (a07*w[6] + a08*w[7] ) ) 87 | - ( (a09*w[8] + a10*w[9] ) + (a11*w[10] + a12*w[11] ) ); 88 | 89 | double y = b00*tmp 90 | + ( (b01*w[0] + b02*w[1]) + (b03*w[2] + b04*w[3] ) ) 91 | + ( (b05*w[4] + b06*w[5]) + (b07*w[6] + b08*w[7] ) ) 92 | + ( (b09*w[8] + b10*w[9]) + (b11*w[10] + b12*w[11] ) ); 93 | 94 | // update state variables: 95 | memmove(&w[1], &w[0], 11*sizeof(double)); 96 | w[0] = tmp; 97 | 98 | return y; 99 | } 100 | 101 | } // end namespace rosic 102 | 103 | #endif // rosic_EllipticQuarterBandFilter_h 104 | -------------------------------------------------------------------------------- /engine/audio/Analyser.cpp: -------------------------------------------------------------------------------- 1 | #include "Analyser.hpp" 2 | #include "Audio.hpp" 3 | 4 | #include "../core/Log.hpp" 5 | 6 | 7 | namespace sns { 8 | 9 | std::string toString(AnalyserSync kind) { 10 | switch (kind) { 11 | case AnalyserSync::None: return "None"; 12 | case AnalyserSync::RiseZero: return "RiseZero"; 13 | case AnalyserSync::FallZero: return "FallZero"; 14 | default: break; 15 | } 16 | return "[AnalyserSync NOT_SET]"; 17 | } 18 | 19 | 20 | Analyser::Analyser() 21 | :m_started(false) 22 | { 23 | configureGraph(10, 100, 0.0f, AnalyserSync::None); 24 | } 25 | 26 | void Analyser::configureGraph(int points, int duration, float offset_factor, AnalyserSync sync) { 27 | m_sync = sync; 28 | 29 | offset_factor = clampTo(offset_factor, 0.0f, 1.0f); 30 | 31 | m_graph_points = points; 32 | 33 | m_graph_duration = std::min(duration, SamplesDuration); 34 | m_graph_duration_samples = int(samplesFromMilliseconds(m_graph_duration)); 35 | 36 | m_graph_offset = int(float(m_samples.capacity()) * offset_factor); 37 | Log::d(TAG, sfmt("points=%d duration=%d graph_offset=%d sync=%s", 38 | points, duration, m_graph_offset, toString(sync))); 39 | } 40 | 41 | void Analyser::generateGraph(std::vector& points) { 42 | std::unique_lock lock(m_samples_mutex); 43 | 44 | if (int(points.size()) != m_graph_points) 45 | points.resize(m_graph_points); 46 | 47 | // we still did not receive enough samples 48 | if (int(m_samples.size()) < m_graph_duration_samples) 49 | return; 50 | 51 | float increment = float(m_graph_duration_samples) / float(m_graph_points); 52 | int start_index = int(m_samples.size()) - m_graph_duration_samples; 53 | start_index = clampAbove(start_index, 0); 54 | 55 | if (m_sync != AnalyserSync::None) { 56 | int index = start_index; 57 | 58 | if (m_sync == AnalyserSync::RiseZero) { 59 | if (m_samples[index] < 0.0f) 60 | while (index && m_samples[index] <= 0.0f) --index; 61 | 62 | if (m_samples[index] > 0.0f) 63 | while (index && m_samples[index] > 0.0f) --index; 64 | } else if (m_sync == AnalyserSync::FallZero) { 65 | if (m_samples[index] > 0.0f) 66 | while (index && m_samples[index] > 0.0f) --index; 67 | 68 | if (m_samples[index] < 0.0f) 69 | while (index && m_samples[index] <= 0.0f) --index; 70 | } 71 | 72 | if (index > 0) 73 | start_index = index; 74 | } 75 | 76 | 77 | start_index = clampAbove(start_index - m_graph_offset, 0); 78 | 79 | 80 | for (int i = 0; i != m_graph_points; ++i) { 81 | float factor = float(i) * increment; 82 | int point_index = int(factor); 83 | 84 | points[i] = m_samples[start_index + point_index]; 85 | } 86 | } 87 | 88 | float Analyser::peak() { 89 | std::unique_lock lock(m_samples_mutex); 90 | constexpr int sample_count = int(samplesFromMilliseconds(150L)); 91 | float max = 0.0f; 92 | 93 | if (int(m_samples.size()) <= sample_count) 94 | return max; 95 | 96 | int counter = 0; 97 | while (counter != sample_count) { 98 | const int index = int(m_samples.size()) - 1 - counter; 99 | const float v = std::abs(m_samples[index]); 100 | if (v > max) 101 | max = v; 102 | ++counter; 103 | } 104 | return max; 105 | } 106 | 107 | void Analyser::start(std::string const& key) { 108 | bool already_started = m_started; 109 | 110 | remove(m_keys, key); 111 | m_keys.push_back(key); 112 | m_started = true; 113 | 114 | if (!already_started) 115 | Log::d(TAG, "start"); 116 | } 117 | 118 | void Analyser::stop(std::string const& key) { 119 | if (m_started) { 120 | remove(m_keys, key); 121 | m_started = !m_keys.empty(); 122 | if (!m_started) 123 | Log::d(TAG, "stop"); 124 | } 125 | } 126 | 127 | bool Analyser::isAccepting() { 128 | return m_started; 129 | } 130 | 131 | void Analyser::push(float sample) { 132 | std::unique_lock lock(m_samples_mutex); 133 | 134 | while (m_samples.size() == m_samples.capacity()) 135 | m_samples.pop_front(); 136 | 137 | m_samples.push_back(sample); 138 | } 139 | } -------------------------------------------------------------------------------- /engine/instrument/tb303/rosic_OnePoleFilter.cpp: -------------------------------------------------------------------------------- 1 | #include "rosic_OnePoleFilter.h" 2 | using namespace rosic; 3 | 4 | //------------------------------------------------------------------------------------------------- 5 | // construction/destruction: 6 | 7 | OnePoleFilter::OnePoleFilter() 8 | { 9 | shelvingGain = 1.0; 10 | setSampleRate(44100.0); // sampleRate = 44100 Hz by default 11 | setMode (0); // bypass by default 12 | setCutoff (20000.0); // cutoff = 20000 Hz by default 13 | reset(); // reset memorized samples to zero 14 | } 15 | 16 | //------------------------------------------------------------------------------------------------- 17 | // parameter settings: 18 | 19 | void OnePoleFilter::setSampleRate(double newSampleRate) 20 | { 21 | if( newSampleRate > 0.0 ) 22 | sampleRate = newSampleRate; 23 | sampleRateRec = 1.0 / sampleRate; 24 | 25 | calcCoeffs(); 26 | return; 27 | } 28 | 29 | void OnePoleFilter::setMode(int newMode) 30 | { 31 | mode = newMode; // 0:bypass, 1:Low Pass, 2:High Pass 32 | calcCoeffs(); 33 | } 34 | 35 | void OnePoleFilter::setCutoff(double newCutoff) 36 | { 37 | if( (newCutoff>0.0) && (newCutoff<=20000.0) ) 38 | cutoff = newCutoff; 39 | else 40 | cutoff = 20000.0; 41 | 42 | calcCoeffs(); 43 | return; 44 | } 45 | 46 | void OnePoleFilter::setShelvingGain(double newGain) 47 | { 48 | if( newGain > 0.0 ) 49 | { 50 | shelvingGain = newGain; 51 | calcCoeffs(); 52 | } 53 | else 54 | DEBUG_BREAK; // this is a linear gain factor and must be >= 0.0 55 | } 56 | 57 | void OnePoleFilter::setShelvingGainInDecibels(double newGain) 58 | { 59 | setShelvingGain(dB2amp(newGain)); 60 | } 61 | 62 | void OnePoleFilter::setCoefficients(double newB0, double newB1, double newA1) 63 | { 64 | b0 = newB0; 65 | b1 = newB1; 66 | a1 = newA1; 67 | } 68 | 69 | void OnePoleFilter::setInternalState(double newX1, double newY1) 70 | { 71 | x1 = newX1; 72 | y1 = newY1; 73 | } 74 | 75 | //------------------------------------------------------------------------------------------------- 76 | //others: 77 | 78 | void OnePoleFilter::calcCoeffs() 79 | { 80 | switch(mode) 81 | { 82 | case LOWPASS: 83 | { 84 | // formula from dspguide: 85 | double x = exp( -2.0 * PI * cutoff * sampleRateRec); 86 | b0 = 1-x; 87 | b1 = 0.0; 88 | a1 = x; 89 | } 90 | break; 91 | case HIGHPASS: 92 | { 93 | // formula from dspguide: 94 | double x = exp( -2.0 * PI * cutoff * sampleRateRec); 95 | b0 = 0.5*(1+x); 96 | b1 = -0.5*(1+x); 97 | a1 = x; 98 | } 99 | break; 100 | case LOWSHELV: 101 | { 102 | // formula from DAFX: 103 | double c = 0.5*(shelvingGain-1.0); 104 | double t = tan(PI*cutoff*sampleRateRec); 105 | double a; 106 | if( shelvingGain >= 1.0 ) 107 | a = (t-1.0)/(t+1.0); 108 | else 109 | a = (t-shelvingGain)/(t+shelvingGain); 110 | 111 | b0 = 1.0 + c + c*a; 112 | b1 = c + c*a + a; 113 | a1 = -a; 114 | } 115 | break; 116 | case HIGHSHELV: 117 | { 118 | // formula from DAFX: 119 | double c = 0.5*(shelvingGain-1.0); 120 | double t = tan(PI*cutoff*sampleRateRec); 121 | double a; 122 | if( shelvingGain >= 1.0 ) 123 | a = (t-1.0)/(t+1.0); 124 | else 125 | a = (shelvingGain*t-1.0)/(shelvingGain*t+1.0); 126 | 127 | b0 = 1.0 + c - c*a; 128 | b1 = a + c*a - c; 129 | a1 = -a; 130 | } 131 | break; 132 | 133 | case ALLPASS: 134 | { 135 | // formula from DAFX: 136 | double t = tan(PI*cutoff*sampleRateRec); 137 | double x = (t-1.0) / (t+1.0); 138 | 139 | b0 = x; 140 | b1 = 1.0; 141 | a1 = -x; 142 | } 143 | break; 144 | 145 | default: // bypass 146 | { 147 | b0 = 1.0; 148 | b1 = 0.0; 149 | a1 = 0.0; 150 | }break; 151 | } 152 | } 153 | 154 | void OnePoleFilter::reset() 155 | { 156 | x1 = 0.0; 157 | y1 = 0.0; 158 | } 159 | -------------------------------------------------------------------------------- /engine/Chainer.cpp: -------------------------------------------------------------------------------- 1 | #include "Chainer.hpp" 2 | #include "core/Log.hpp" 3 | #include "Engine.hpp" 4 | 5 | namespace sns { 6 | 7 | Chainer::Chainer() 8 | :TAG("Chainer"), 9 | m_engine(nullptr) 10 | { 11 | apply(Configuration()); 12 | panic(); 13 | } 14 | 15 | void Chainer::setEngine(Engine* engine) { 16 | m_engine = engine; 17 | } 18 | 19 | Chainer::State Chainer::state() { 20 | return m_state; 21 | } 22 | 23 | bool Chainer::advanceToNext() { 24 | if (!m_cfg.valid) 25 | return false; 26 | 27 | if (m_state.link_run < (m_cfg.chain[m_state.link_index].runs - 1)) { 28 | m_state.link_run++; 29 | return false; 30 | } 31 | 32 | // move to next link 33 | int current = m_state.link_index; 34 | 35 | m_state.link_index = (m_state.link_index + 1) % m_cfg.chain.size(); 36 | m_state.link_run = 0; 37 | 38 | while (!m_cfg.chain[m_state.link_index].valid) 39 | m_state.link_index = (m_state.link_index + 1) % m_cfg.chain.size(); 40 | 41 | bool looped = m_state.link_index < current; 42 | 43 | return looped; 44 | } 45 | 46 | void Chainer::apply(Configuration const& cfg) { 47 | if (cfg.action != Sequencer::Action::None) 48 | Log::i(TAG, sfmt("Chainer::action=%s", toString(cfg.action))); 49 | 50 | if (cfg.action == Sequencer::Action::Stop || cfg.action == Sequencer::Action::Pause) { 51 | Sequencer::Configuration sequence; 52 | sequence.action = Sequencer::Action::Stop; 53 | m_engine->sequencer().apply(sequence, true); 54 | } 55 | 56 | m_cfg = cfg; 57 | m_cfg.valid = false; 58 | m_state.playing = false; 59 | 60 | for (auto& chain : m_cfg.chain) { 61 | chain.valid = false; 62 | 63 | if (chain.name.empty() || chain.runs <= 0) 64 | continue; 65 | 66 | for (InstrumentId instrument = InstrumentStart; instrument != InstrumentCount; ++instrument) { 67 | auto const& idata = chain.sequence.instruments[instrument]; 68 | if (idata.muted) continue; 69 | 70 | chain.valid = true; 71 | m_cfg.valid = true; 72 | 73 | // for (auto const& [step_note, note_mode] : idata.steps) { 74 | // auto [step, note] = step_note; 75 | // if (note_mode == Sequencer::NoteMode::Off) 76 | // continue; 77 | // if (step >= chain.sequence.step_count) 78 | // continue; 79 | 80 | // chain.valid = true; 81 | // m_cfg.valid = true; 82 | // break; 83 | // } 84 | 85 | if (chain.valid) 86 | break; 87 | } 88 | } 89 | } 90 | 91 | bool Chainer::next() { 92 | bool report_state = (m_cfg.action != Sequencer::Action::None); 93 | 94 | bool play_link = false; 95 | 96 | if (m_state.playing) { 97 | if (!m_engine->sequencer().state().playing) { 98 | Log::i(TAG, sfmt("%d/%d done", m_state.link_index, m_state.link_run)); 99 | 100 | bool looped = advanceToNext(); 101 | 102 | //if (looped) { 103 | // m_state.playing = false; 104 | //} 105 | //else 106 | { 107 | play_link = true; 108 | } 109 | report_state = true; 110 | } 111 | } 112 | 113 | if (m_cfg.action != Sequencer::Action::None) { 114 | 115 | if (m_cfg.action == Sequencer::Action::Stop || m_cfg.action == Sequencer::Action::Pause) { 116 | m_state.playing = false; 117 | 118 | m_state.link_index = 0; 119 | m_state.link_run = 0; 120 | 121 | if (m_cfg.action_link >= 0) 122 | m_state.link_index = m_cfg.action_link; 123 | 124 | } 125 | else if (m_cfg.valid && (m_cfg.action == Sequencer::Action::Play || m_cfg.action == Sequencer::Action::PlayOnce)) { 126 | m_state.playing = true; 127 | 128 | m_state.link_index = 0; 129 | m_state.link_run = 0; 130 | 131 | if (m_cfg.action_link >= 0) 132 | m_state.link_index = m_cfg.action_link; 133 | 134 | if (!m_cfg.chain[m_state.link_index].valid) 135 | advanceToNext(); 136 | 137 | play_link = true; 138 | } 139 | m_cfg.action = Sequencer::Action::None; 140 | } 141 | 142 | 143 | if (play_link) { 144 | Sequencer::Configuration sequence = m_cfg.chain[m_state.link_index].sequence; 145 | sequence.action = Sequencer::Action::PlayOnce; 146 | m_engine->sequencer().apply(sequence, true); 147 | } 148 | 149 | return report_state; 150 | } 151 | 152 | void Chainer::panic() { 153 | m_state = State(); 154 | } 155 | 156 | } -------------------------------------------------------------------------------- /engine/instrument/tb303/rosic_OnePoleFilter.h: -------------------------------------------------------------------------------- 1 | #ifndef rosic_OnePoleFilter_h 2 | #define rosic_OnePoleFilter_h 3 | 4 | // rosic-indcludes: 5 | #include "rosic_RealFunctions.h" 6 | 7 | namespace rosic 8 | { 9 | 10 | /** 11 | 12 | This is an implementation of a simple one-pole filter unit. 13 | 14 | */ 15 | 16 | class OnePoleFilter 17 | { 18 | 19 | public: 20 | 21 | /** This is an enumeration of the available filter modes. */ 22 | enum modes 23 | { 24 | BYPASS = 0, 25 | LOWPASS, 26 | HIGHPASS, 27 | LOWSHELV, 28 | HIGHSHELV, 29 | ALLPASS 30 | }; 31 | // \todo (maybe): let the user choose between LP/HP versions obtained via bilinear trafo and 32 | // impulse invariant trafo 33 | 34 | //--------------------------------------------------------------------------------------------- 35 | // construction/destruction: 36 | 37 | /** Constructor. */ 38 | OnePoleFilter(); 39 | 40 | //--------------------------------------------------------------------------------------------- 41 | // parameter settings: 42 | 43 | /** Sets the sample-rate. */ 44 | void setSampleRate(double newSampleRate); 45 | 46 | /** Chooses the filter mode. See the enumeration for available modes. */ 47 | void setMode(int newMode); 48 | 49 | /** Sets the cutoff-frequency for this filter. */ 50 | void setCutoff(double newCutoff); 51 | 52 | /** This will set the time constant 'tau' for the case, when lowpass mode is chosen. This is 53 | the time, it takes for the impulse response to die away to 1/e = 0.368... or equivalently, the 54 | time it takes for the step response to raise to 1-1/e = 0.632... */ 55 | void setLowpassTimeConstant(double newTimeConstant) { setCutoff(1.0/(2*PI*newTimeConstant)); } 56 | 57 | /** Sets the gain factor for the shelving modes (this is not in decibels). */ 58 | void setShelvingGain(double newGain); 59 | 60 | /** Sets the gain for the shelving modes in decibels. */ 61 | void setShelvingGainInDecibels(double newGain); 62 | 63 | /** Sets the filter coefficients manually. */ 64 | void setCoefficients(double newB0, double newB1, double newA1); 65 | 66 | /** Sets up the internal state variables for both channels. */ 67 | void setInternalState(double newX1, double newY1); 68 | 69 | //--------------------------------------------------------------------------------------------- 70 | // inquiry 71 | 72 | /** Returns the cutoff-frequency. */ 73 | double getCutoff() const { return cutoff; } 74 | 75 | //--------------------------------------------------------------------------------------------- 76 | // audio processing: 77 | 78 | /** Calculates a single filtered output-sample. */ 79 | INLINE double getSample(double in); 80 | 81 | //--------------------------------------------------------------------------------------------- 82 | // others: 83 | 84 | /** Resets the internal buffers (for the \f$ x[n-1], y[n-1] \f$-samples) to zero. */ 85 | void reset(); 86 | 87 | //============================================================================================= 88 | 89 | protected: 90 | 91 | // buffering: 92 | double x1, y1; 93 | 94 | // filter coefficients: 95 | double b0; // feedforward coeffs 96 | double b1; 97 | double a1; // feedback coeff 98 | 99 | // filter parameters: 100 | double cutoff; 101 | double shelvingGain; 102 | int mode; 103 | 104 | double sampleRate; 105 | double sampleRateRec; // reciprocal of the sampleRate 106 | 107 | // internal functions: 108 | void calcCoeffs(); // calculates filter coefficients from filter parameters 109 | 110 | }; 111 | 112 | //----------------------------------------------------------------------------------------------- 113 | // inlined functions: 114 | 115 | INLINE double OnePoleFilter::getSample(double in) 116 | { 117 | // calculate the output sample: 118 | y1 = b0*in + b1*x1 + a1*y1 + TINY; 119 | 120 | // update the buffer variables: 121 | x1 = in; 122 | 123 | return y1; 124 | } 125 | 126 | } // end namespace rosic 127 | 128 | #endif // rosic_OnePoleFilter_h 129 | --------------------------------------------------------------------------------