From 1374c47afd6e2a0759c980d414d4389e30f34dad Mon Sep 17 00:00:00 2001 From: John Chadwick Date: Wed, 28 Sep 2022 04:12:19 -0400 Subject: [PATCH 01/50] Increase command data size to 1024. (Fixes #54) --- brain/src/spiralcore/command_ring_buffer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/brain/src/spiralcore/command_ring_buffer.h b/brain/src/spiralcore/command_ring_buffer.h index 2df8608..f7e3797 100644 --- a/brain/src/spiralcore/command_ring_buffer.h +++ b/brain/src/spiralcore/command_ring_buffer.h @@ -19,7 +19,7 @@ #ifndef COMMAND_RING_BUFFER #define COMMAND_RING_BUFFER -static const unsigned int COMMAND_DATA_SIZE = 128; +static const unsigned int COMMAND_DATA_SIZE = 1024; class command_ring_buffer : public ring_buffer { From cdb6a912c08f919fab67f57c6e1a34b339272c3a Mon Sep 17 00:00:00 2001 From: Dave Griffiths Date: Wed, 28 Sep 2022 12:38:20 +0100 Subject: [PATCH 02/50] windows version bump --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 75c25b0..f6e197f 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ written to run on a couple of computers!) you will have to bear with us as we gradually stabilise things based on your feedback. There might currently be problems running it on 64bit Windows. -* **Windows**: [samplebrain_0.18.1_win.zip](https://static.thentrythis.org/samplebrain/samplebrain_0.18.1_win.zip) +* **Windows**: [samplebrain_0.18.2_win.zip](https://static.thentrythis.org/samplebrain/samplebrain_0.18.2_win.zip) * **Mac (intel/m1)**: [samplebrain_0.18.1_macintel.zip](https://static.thentrythis.org/samplebrain/samplebrain_0.18.1_macintel.app.zip) Thank you to [Nik Gaffney](http://fo.am) for help with the Apple builds @@ -70,6 +70,7 @@ If you'd like the right font, optionally: # Old/broken/spurious binaries * **Windows* *: [samplebrain_0.18_win.zip](https://static.thentrythis.org/samplebrain/samplebrain_0.18_win.zip) +* **Windows**: [samplebrain_0.18.1_win.zip](https://static.thentrythis.org/samplebrain/samplebrain_0.18.1_win.zip) * **Mac (intel)**: [samplebrain_0.18_macintel.zip](https://static.thentrythis.org/samplebrain/samplebrain_0.18_macintel.zip) * **Mac (m1)**: [samplebrain_0.18_m1_v2.dmg](https://static.thentrythis.org/samplebrain/samplebrain_0.18_m1_v2.dmg) From d783b8c16aace1337401ed1b49e3cfea81282f56 Mon Sep 17 00:00:00 2001 From: Dave Griffiths Date: Wed, 28 Sep 2022 12:42:48 +0100 Subject: [PATCH 03/50] fixed formatting --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f6e197f..eff94ba 100644 --- a/README.md +++ b/README.md @@ -69,8 +69,8 @@ If you'd like the right font, optionally: # Old/broken/spurious binaries -* **Windows* *: [samplebrain_0.18_win.zip](https://static.thentrythis.org/samplebrain/samplebrain_0.18_win.zip) * **Windows**: [samplebrain_0.18.1_win.zip](https://static.thentrythis.org/samplebrain/samplebrain_0.18.1_win.zip) +* **Windows**: [samplebrain_0.18_win.zip](https://static.thentrythis.org/samplebrain/samplebrain_0.18_win.zip) * **Mac (intel)**: [samplebrain_0.18_macintel.zip](https://static.thentrythis.org/samplebrain/samplebrain_0.18_macintel.zip) * **Mac (m1)**: [samplebrain_0.18_m1_v2.dmg](https://static.thentrythis.org/samplebrain/samplebrain_0.18_m1_v2.dmg) From 30d93be49c700f469e662aafd17bf71419b57a6a Mon Sep 17 00:00:00 2001 From: Owen D'Aprile Date: Tue, 27 Sep 2022 21:21:40 -0400 Subject: [PATCH 04/50] linux: update desktop file Remove unnecessary keys, tweak the comment, and add categories. --- desktop/samplebrain.desktop | 10 ++++------ desktop/samplebrain.desktop~ | 7 ------- 2 files changed, 4 insertions(+), 13 deletions(-) delete mode 100644 desktop/samplebrain.desktop~ diff --git a/desktop/samplebrain.desktop b/desktop/samplebrain.desktop index 20d400b..0b94e56 100644 --- a/desktop/samplebrain.desktop +++ b/desktop/samplebrain.desktop @@ -1,10 +1,8 @@ [Desktop Entry] -Encoding=UTF-8 Type=Application Name=Samplebrain -Comment=A sample masher designed by Aphex Twin -Exec=samplebrain +Comment=A custom sample mashing app designed by Aphex Twin Icon=samplebrain -Terminal=false -Categories=GNOME;Application; -StartupNotify=true \ No newline at end of file +Exec=samplebrain +Categories=AudioVideo;Music +StartupNotify=true diff --git a/desktop/samplebrain.desktop~ b/desktop/samplebrain.desktop~ deleted file mode 100644 index fd7a886..0000000 --- a/desktop/samplebrain.desktop~ +++ /dev/null @@ -1,7 +0,0 @@ -[Desktop Entry] -Type=Application -Version=1.0 -Name=Samplebrain -GenericName="Samplebrain" -Exec=samplebrain -Icon=samplebrain From 672fb1868fd9de579c2ade0d6d5311668949cfe4 Mon Sep 17 00:00:00 2001 From: Owen D'Aprile Date: Tue, 27 Sep 2022 21:22:17 -0400 Subject: [PATCH 05/50] linux: add a screenshot for the metadata AppStream metadata requires a screenshot. This screenshot will be used in the graphical app stores. --- desktop/screenshots/01-main-with-project.png | Bin 0 -> 116116 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 desktop/screenshots/01-main-with-project.png diff --git a/desktop/screenshots/01-main-with-project.png b/desktop/screenshots/01-main-with-project.png new file mode 100644 index 0000000000000000000000000000000000000000..94b4e803f05f334ff111673e6b802258446a239f GIT binary patch literal 116116 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bmDO#KEEs;cjcic;8%k?!RNBHqEzR~N-yXPQ1T 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 literal 0 HcmV?d00001 From dc0c047ad6b9ac67ea97209048ea98adfdaf94a7 Mon Sep 17 00:00:00 2001 From: Owen D'Aprile Date: Tue, 27 Sep 2022 21:23:12 -0400 Subject: [PATCH 06/50] linux: add AppStream metadata Adds an AppStream metainfo file that is used to display the app in graphical app stores on Linux. This is also required for submission to Flathub. --- .../org.thentrythis.Samplebrain.metainfo.xml | 42 +++++++++++++++++++ samplebrain.pro | 4 +- 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 desktop/org.thentrythis.Samplebrain.metainfo.xml diff --git a/desktop/org.thentrythis.Samplebrain.metainfo.xml b/desktop/org.thentrythis.Samplebrain.metainfo.xml new file mode 100644 index 0000000..63babad --- /dev/null +++ b/desktop/org.thentrythis.Samplebrain.metainfo.xml @@ -0,0 +1,42 @@ + + + org.thentrythis.Samplebrain + + Samplebrain + A custom sample mashing app designed by Aphex Twin + + CC0-1.0 + GPL-2.0-only + + +

+ Samplebrain chops samples up into a 'brain' of interconnected small sections called blocks which are connected into a network by similarity. It processes a target sample, chopping it up into blocks in the same way, and tries to match each block with one in it's brain to play in realtime. +

+

+ This allows you to interpret a sound with a different one. As we worked on it (during 2015 and 2016) we gradually added more and more tweakable parameters until it became slightly out of control. +

+
+ + samplebrain.desktop + + + AudioVideo + Music + + + + + https://thentrythis.org/projects/samplebrain/ + https://gitlab.com/then-try-this/samplebrain/-/issues + https://gitlab.com/then-try-this/samplebrain + + + + https://gitlab.com/then-try-this/samplebrain/-/raw/main/desktop/screenshots/01-main-with-project.png + + + + + + +
diff --git a/samplebrain.pro b/samplebrain.pro index c7df563..22799fb 100644 --- a/samplebrain.pro +++ b/samplebrain.pro @@ -61,6 +61,8 @@ unix:desktopfile.path = $$PREFIX/share/applications/ unix:desktopfile.files = desktop/samplebrain.desktop unix:iconfile.path = $$PREFIX/share/icons/hicolor/scalable/apps unix:iconfile.files = desktop/samplebrain.svg +unix:metainfofile.path = $$PREFIX/share/metainfo +unix:metainfofile.files = desktop/org.thentrythis.Samplebrain.metainfo.xml target.path = $$PREFIX/bin -INSTALLS += target desktopfile iconfile +INSTALLS += target desktopfile iconfile metainfofile From a7b866b4fce05d6a9dd2611c8bf50063304b6202 Mon Sep 17 00:00:00 2001 From: Alex McLean Date: Thu, 29 Sep 2022 10:44:56 +0000 Subject: [PATCH 07/50] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index eff94ba..4caf0c2 100644 --- a/README.md +++ b/README.md @@ -157,3 +157,6 @@ License version 2 (see LICENCE). Written by [Dave Griffiths at Then Try This](http://thentrythis.org). +## Links + +To find related tech like [CataRT](http://imtr.ircam.fr/imtr/CataRT) and [sCrAmBlEd?HaCkZ!](https://www.youtube.com/watch?v=eRlhKaxcKpA) search up [granular synthesis](http://granularsynthesis.com/guide.php), [concatenative synthesis](https://hal.archives-ouvertes.fr/hal-01161337), [neural audio synthesis](https://github.com/acids-ircam/RAVE), [sinewave speech](http://www.lifesci.sussex.ac.uk/home/Chris_Darwin/SWS/), audio mosaicing and plundermatics. From 287ba85c1485c86e93a4a763daaffd6c52419973 Mon Sep 17 00:00:00 2001 From: Alex McLean Date: Thu, 29 Sep 2022 11:29:41 +0000 Subject: [PATCH 08/50] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4caf0c2..e845df2 100644 --- a/README.md +++ b/README.md @@ -159,4 +159,4 @@ Written by [Dave Griffiths at Then Try This](http://thentrythis.org). ## Links -To find related tech like [CataRT](http://imtr.ircam.fr/imtr/CataRT) and [sCrAmBlEd?HaCkZ!](https://www.youtube.com/watch?v=eRlhKaxcKpA) search up [granular synthesis](http://granularsynthesis.com/guide.php), [concatenative synthesis](https://hal.archives-ouvertes.fr/hal-01161337), [neural audio synthesis](https://github.com/acids-ircam/RAVE), [sinewave speech](http://www.lifesci.sussex.ac.uk/home/Chris_Darwin/SWS/), audio mosaicing and plundermatics. +To find related tech like [CataRT](http://imtr.ircam.fr/imtr/CataRT), [bbcut2]](https://composerprogrammer.com/bbcut2.html), [eargram](https://sites.google.com/site/eargram/) and [sCrAmBlEd?HaCkZ!](https://www.youtube.com/watch?v=eRlhKaxcKpA) search up [granular synthesis](http://granularsynthesis.com/guide.php), [concatenative synthesis](https://hal.archives-ouvertes.fr/hal-01161337), [neural audio synthesis](https://github.com/acids-ircam/RAVE), [sinewave speech](http://www.lifesci.sussex.ac.uk/home/Chris_Darwin/SWS/), automated breakbeat cutting, audio mosaicing and plundermatics. From 74eb5b0b9bb3382b93c7cc7634b471b7abb1c89d Mon Sep 17 00:00:00 2001 From: Alex McLean Date: Thu, 29 Sep 2022 11:30:07 +0000 Subject: [PATCH 09/50] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e845df2..6c984d4 100644 --- a/README.md +++ b/README.md @@ -159,4 +159,4 @@ Written by [Dave Griffiths at Then Try This](http://thentrythis.org). ## Links -To find related tech like [CataRT](http://imtr.ircam.fr/imtr/CataRT), [bbcut2]](https://composerprogrammer.com/bbcut2.html), [eargram](https://sites.google.com/site/eargram/) and [sCrAmBlEd?HaCkZ!](https://www.youtube.com/watch?v=eRlhKaxcKpA) search up [granular synthesis](http://granularsynthesis.com/guide.php), [concatenative synthesis](https://hal.archives-ouvertes.fr/hal-01161337), [neural audio synthesis](https://github.com/acids-ircam/RAVE), [sinewave speech](http://www.lifesci.sussex.ac.uk/home/Chris_Darwin/SWS/), automated breakbeat cutting, audio mosaicing and plundermatics. +To find related tech like [CataRT](http://imtr.ircam.fr/imtr/CataRT), [bbcut2](https://composerprogrammer.com/bbcut2.html), [eargram](https://sites.google.com/site/eargram/) and [sCrAmBlEd?HaCkZ!](https://www.youtube.com/watch?v=eRlhKaxcKpA) search up [granular synthesis](http://granularsynthesis.com/guide.php), [concatenative synthesis](https://hal.archives-ouvertes.fr/hal-01161337), [neural audio synthesis](https://github.com/acids-ircam/RAVE), [sinewave speech](http://www.lifesci.sussex.ac.uk/home/Chris_Darwin/SWS/), automated breakbeat cutting, audio mosaicing and plundermatics. From e3619746a4c246919f81a92966084b8e1271ccfc Mon Sep 17 00:00:00 2001 From: Alex McLean Date: Thu, 29 Sep 2022 11:36:21 +0000 Subject: [PATCH 10/50] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6c984d4..7b62883 100644 --- a/README.md +++ b/README.md @@ -159,4 +159,4 @@ Written by [Dave Griffiths at Then Try This](http://thentrythis.org). ## Links -To find related tech like [CataRT](http://imtr.ircam.fr/imtr/CataRT), [bbcut2](https://composerprogrammer.com/bbcut2.html), [eargram](https://sites.google.com/site/eargram/) and [sCrAmBlEd?HaCkZ!](https://www.youtube.com/watch?v=eRlhKaxcKpA) search up [granular synthesis](http://granularsynthesis.com/guide.php), [concatenative synthesis](https://hal.archives-ouvertes.fr/hal-01161337), [neural audio synthesis](https://github.com/acids-ircam/RAVE), [sinewave speech](http://www.lifesci.sussex.ac.uk/home/Chris_Darwin/SWS/), automated breakbeat cutting, audio mosaicing and plundermatics. +To find related tech like [CataRT](http://imtr.ircam.fr/imtr/CataRT), [bbcut2](https://composerprogrammer.com/bbcut2.html), [eargram](https://sites.google.com/site/eargram/) and [sCrAmBlEd?HaCkZ!](https://www.youtube.com/watch?v=eRlhKaxcKpA) search up [granular synthesis](http://granularsynthesis.com/guide.php), [concatenative synthesis](https://hal.archives-ouvertes.fr/hal-01161337), [neural audio synthesis](https://github.com/acids-ircam/RAVE), [sinewave speech](http://www.lifesci.sussex.ac.uk/home/Chris_Darwin/SWS/), automated breakbeat cutting, audio mosaicing and plunderphonics/plundermatics. From 99e59a6f11b7121c89889553a5bfb690b17a9ed0 Mon Sep 17 00:00:00 2001 From: Owen D'Aprile Date: Thu, 29 Sep 2022 17:45:25 -0400 Subject: [PATCH 11/50] readme: add Flathub download badge --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7b62883..cd6a80e 100644 --- a/README.md +++ b/README.md @@ -57,8 +57,10 @@ binary you need to tell your mac it's ok: Go to System Preferences > Security & Privacy > General. At the bottom of the window, select "Allow apps to be downloaded from Anywhere". -**Linux install (Ubuntu)** +### Linux +Download on Flathub +#### Ubuntu $ sudo add-apt-repository ppa:thentrythis/samplebrain $ sudo apt update $ sudo apt install samplebrain From 524cd5894598fca8e6c42f7f869209e39bc2381f Mon Sep 17 00:00:00 2001 From: Dave Griffiths Date: Fri, 30 Sep 2022 12:02:56 +0100 Subject: [PATCH 12/50] version bump --- .gitignore | 3 +++ gui/samplebrain.ui | 43 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b7e2626 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.*~ +*.o + diff --git a/gui/samplebrain.ui b/gui/samplebrain.ui index ff82d63..fc4d160 100644 --- a/gui/samplebrain.ui +++ b/gui/samplebrain.ui @@ -11,7 +11,7 @@ - samplebrain 0.18.1 + samplebrain 0.18.3 @@ -20,6 +20,7 @@ Comic Sans MS + 75 true @@ -39,6 +40,7 @@ Comic Sans MS 20 + 75 true @@ -55,6 +57,7 @@ Comic Sans MS 9 + 75 true @@ -114,6 +117,7 @@ Comic Sans MS 9 + 75 true @@ -173,6 +177,7 @@ Comic Sans MS 9 + 75 true @@ -195,6 +200,7 @@ Comic Sans MS + 75 true @@ -221,6 +227,7 @@ Comic Sans MS + 75 true @@ -249,6 +256,7 @@ Comic Sans MS 9 + 75 true @@ -311,6 +319,7 @@ Comic Sans MS 9 + 75 true @@ -373,6 +382,7 @@ Comic Sans MS 9 + 75 true @@ -435,6 +445,7 @@ Comic Sans MS 9 + 75 true @@ -503,6 +514,7 @@ Comic Sans MS 9 + 75 true @@ -545,6 +557,7 @@ Comic Sans MS 9 + 75 true @@ -607,6 +620,7 @@ Comic Sans MS 9 + 75 true @@ -690,6 +704,7 @@ Comic Sans MS 20 + 75 true @@ -703,6 +718,7 @@ Comic Sans MS + 75 true @@ -718,6 +734,7 @@ Comic Sans MS + 75 true @@ -748,6 +765,7 @@ Comic Sans MS + 75 true @@ -778,6 +796,7 @@ Comic Sans MS + 75 true @@ -837,6 +856,7 @@ Comic Sans MS + 75 true @@ -853,6 +873,7 @@ Comic Sans MS + 75 true @@ -870,6 +891,7 @@ Comic Sans MS 20 + 75 true @@ -886,6 +908,7 @@ Comic Sans MS 9 + 75 true @@ -942,6 +965,7 @@ Comic Sans MS 9 + 75 true @@ -998,6 +1022,7 @@ Comic Sans MS 9 + 75 true @@ -1081,6 +1106,7 @@ Comic Sans MS 20 + 75 true @@ -1127,7 +1153,7 @@ 0 0 - 600 + 424 198 @@ -1146,6 +1172,7 @@ Comic Sans MS + 75 true @@ -1159,6 +1186,7 @@ Comic Sans MS + 75 true @@ -1172,6 +1200,7 @@ Comic Sans MS + 75 true @@ -1189,6 +1218,7 @@ Comic Sans MS + 75 true @@ -1219,6 +1249,7 @@ Comic Sans MS + 75 true @@ -1249,6 +1280,7 @@ Comic Sans MS + 75 true @@ -1308,6 +1340,7 @@ Comic Sans MS + 75 true @@ -1323,6 +1356,7 @@ Comic Sans MS + 75 true @@ -1336,6 +1370,7 @@ Comic Sans MS + 75 true @@ -1399,6 +1434,7 @@ Comic Sans MS + 75 true @@ -1425,6 +1461,7 @@ Comic Sans MS + 75 true @@ -1500,6 +1537,7 @@ Comic Sans MS + 75 true @@ -1513,6 +1551,7 @@ Comic Sans MS + 75 true From a2b77951cab44568097cd174697d2fb039344ba2 Mon Sep 17 00:00:00 2001 From: Dave Griffiths Date: Fri, 30 Sep 2022 12:48:36 +0100 Subject: [PATCH 13/50] new version links --- README.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index cd6a80e..e2ea65d 100644 --- a/README.md +++ b/README.md @@ -46,17 +46,19 @@ As this is experimental non-commercial software (only originally written to run on a couple of computers!) you will have to bear with us as we gradually stabilise things based on your feedback. There might currently be problems running it on 64bit Windows. + +* **Windows**: [samplebrain_0.18.3_win.zip](https://static.thentrythis.org/samplebrain/samplebrain_0.18.3_win.zip) +* **Mac (intel/m1)**: [samplebrain_0.18.3_macintel.zip](https://static.thentrythis.org/samplebrain/samplebrain_0.18.3_macintel.app.zip) + +(New 0.18.3 release fixes loading samples from paths longer than 255 characters) -* **Windows**: [samplebrain_0.18.2_win.zip](https://static.thentrythis.org/samplebrain/samplebrain_0.18.2_win.zip) -* **Mac (intel/m1)**: [samplebrain_0.18.1_macintel.zip](https://static.thentrythis.org/samplebrain/samplebrain_0.18.1_macintel.app.zip) - -Thank you to [Nik Gaffney](http://fo.am) for help with the Apple builds - Mac note: As this software is not on the apple store, to run the binary you need to tell your mac it's ok: Go to System Preferences > Security & Privacy > General. At the bottom of the window, select "Allow apps to be downloaded from Anywhere". +Thank you to [Nik Gaffney](http://fo.am) for help with the Apple builds + ### Linux Download on Flathub @@ -70,9 +72,12 @@ If you'd like the right font, optionally: $ sudo apt install ttf-mscorefonts-installer # Old/broken/spurious binaries - + +* **Windows**: [samplebrain_0.18.2_win.zip](https://static.thentrythis.org/samplebrain/samplebrain_0.18.2_win.zip) * **Windows**: [samplebrain_0.18.1_win.zip](https://static.thentrythis.org/samplebrain/samplebrain_0.18.1_win.zip) * **Windows**: [samplebrain_0.18_win.zip](https://static.thentrythis.org/samplebrain/samplebrain_0.18_win.zip) + +* **Mac (intel/m1)**: [samplebrain_0.18.1_macintel.zip](https://static.thentrythis.org/samplebrain/samplebrain_0.18.1_macintel.app.zip) * **Mac (intel)**: [samplebrain_0.18_macintel.zip](https://static.thentrythis.org/samplebrain/samplebrain_0.18_macintel.zip) * **Mac (m1)**: [samplebrain_0.18_m1_v2.dmg](https://static.thentrythis.org/samplebrain/samplebrain_0.18_m1_v2.dmg) From fb8d607e2dc11a1d9cdef736938548ad887f4013 Mon Sep 17 00:00:00 2001 From: Dave Griffiths Date: Fri, 30 Sep 2022 21:08:53 +0100 Subject: [PATCH 14/50] started making samplerate and buffer size configurable for audio device selection --- app/audio_thread.cpp | 7 +- app/audio_thread.h | 10 ++- app/process_thread.cpp | 94 +++++++++++------------ brain/src/block.cpp | 8 +- brain/src/block.h | 2 +- brain/src/brain.cpp | 28 ------- brain/src/fft.cpp | 22 ++---- brain/src/fft.h | 3 +- brain/src/mfcc.cpp | 2 +- brain/src/mfcc.h | 8 +- brain/src/spiralcore/audio.cpp | 5 +- brain/src/spiralcore/audio.h | 12 +-- brain/src/spiralcore/portaudio_client.cpp | 18 ++++- brain/src/spiralcore/portaudio_client.h | 2 +- brain/src/status.cpp | 4 + brain/src/status.h | 11 +-- 16 files changed, 112 insertions(+), 124 deletions(-) diff --git a/app/audio_thread.cpp b/app/audio_thread.cpp index 8e87fe3..a6307b4 100644 --- a/app/audio_thread.cpp +++ b/app/audio_thread.cpp @@ -26,7 +26,9 @@ audio_thread::audio_thread(process_thread &p) : m_process_thread(p), m_brain_mutex(p.m_brain_mutex), m_stereo_mode(false), - m_mic_mode(false) + m_mic_mode(false), + m_bufsize(2048), + m_samplerate(44100) { start_audio(); pthread_mutex_lock(m_brain_mutex); @@ -48,8 +50,7 @@ audio_thread::~audio_thread() { void audio_thread::start_audio() { if (m_audio_device!=NULL) delete m_audio_device; - m_audio_device = new audio_device("samplebrain",48000,2048); - //m_audio_device = new audio_device("samplebrain",48000,2048*4); + m_audio_device = new audio_device("samplebrain",m_samplerate,m_bufsize); m_audio_device->m_client.set_callback(run_audio, this); } diff --git a/app/audio_thread.h b/app/audio_thread.h index dcb3b81..0c00911 100644 --- a/app/audio_thread.h +++ b/app/audio_thread.h @@ -24,8 +24,8 @@ namespace spiralcore { -class audio_thread { -public: + class audio_thread { + public: audio_thread(process_thread &p); ~audio_thread(); @@ -38,7 +38,7 @@ public: renderer *m_right_renderer; block_stream *m_block_stream; -private: + private: void start_audio(); OSC_server m_osc; @@ -46,6 +46,8 @@ private: pthread_mutex_t* m_brain_mutex; bool m_stereo_mode; bool m_mic_mode; -}; + u32 m_bufsize; + u32 m_samplerate; + }; } diff --git a/app/process_thread.cpp b/app/process_thread.cpp index 3687da8..fec26f2 100644 --- a/app/process_thread.cpp +++ b/app/process_thread.cpp @@ -63,82 +63,82 @@ void process_thread::process() { string name = cmd.m_name; //cerr<reset(); - m_right_renderer->reset(); - pthread_mutex_unlock(m_brain_mutex); + pthread_mutex_lock(m_brain_mutex); + m_source.init(m_source_block_size, m_source_overlap, m_window_type); + search_params p(1,0,0,100,0); + m_source.build_synapses_fixed(p); + m_left_renderer->reset(); + m_right_renderer->reset(); + pthread_mutex_unlock(m_brain_mutex); } if (name=="/load_target") { - pthread_mutex_lock(m_brain_mutex); - m_left_target.clear_sounds(); - m_left_target.load_sound(cmd.get_string(0),brain::LEFT); - m_right_target.clear_sounds(); - m_right_target.load_sound(cmd.get_string(0),brain::RIGHT); - pthread_mutex_unlock(m_brain_mutex); + pthread_mutex_lock(m_brain_mutex); + m_left_target.clear_sounds(); + m_left_target.load_sound(cmd.get_string(0),brain::LEFT); + m_right_target.clear_sounds(); + m_right_target.load_sound(cmd.get_string(0),brain::RIGHT); + pthread_mutex_unlock(m_brain_mutex); } if (name=="/target_block_size") { - m_target_block_size = cmd.get_int(0); - m_block_stream->init(m_target_block_size, m_target_overlap, m_target_window_type); + m_target_block_size = cmd.get_int(0); + m_block_stream->init(m_target_block_size, m_target_overlap, m_target_window_type); } if (name=="/target_overlap") { - m_target_overlap = m_target_block_size*cmd.get_float(0); - m_block_stream->init(m_target_block_size, m_target_overlap, m_target_window_type); + m_target_overlap = m_target_block_size*cmd.get_float(0); + m_block_stream->init(m_target_block_size, m_target_overlap, m_target_window_type); } if (name=="/generate_target") { - pthread_mutex_lock(m_brain_mutex); - m_left_target.init(m_target_block_size, m_target_overlap, m_target_window_type); - m_right_target.init(m_target_block_size, m_target_overlap, m_target_window_type); - // probably elsewhere - m_block_stream->init(m_target_block_size, m_target_overlap, m_target_window_type); - pthread_mutex_unlock(m_brain_mutex); + pthread_mutex_lock(m_brain_mutex); + m_left_target.init(m_target_block_size, m_target_overlap, m_target_window_type); + m_right_target.init(m_target_block_size, m_target_overlap, m_target_window_type); + // probably elsewhere + m_block_stream->init(m_target_block_size, m_target_overlap, m_target_window_type); + pthread_mutex_unlock(m_brain_mutex); } if (name=="/window_type") { - m_window_type=(window::type)cmd.get_int(0); + m_window_type=(window::type)cmd.get_int(0); } if (name=="/target_window_type") { - m_target_window_type=(window::type)cmd.get_int(0); - m_block_stream->init(m_target_block_size, m_target_overlap, m_target_window_type); + m_target_window_type=(window::type)cmd.get_int(0); + m_block_stream->init(m_target_block_size, m_target_overlap, m_target_window_type); } if (name=="/load_brain") { - load_source(cmd.get_string(0)); + load_source(cmd.get_string(0)); } if (name=="/save_brain") { - save_source(cmd.get_string(0)); + save_source(cmd.get_string(0)); } if (name=="/load_session") { - load_session(cmd.get_string(0)); + load_session(cmd.get_string(0)); } if (name=="/save_session") { - save_session(cmd.get_string(0)); + save_session(cmd.get_string(0)); } } #ifdef WIN32 @@ -181,9 +181,6 @@ void process_thread::load_session(const std::string &filename) { ifs||m_source_block_size||m_source_overlap; ifs||m_target_block_size||m_target_overlap; ifs||m_window_type||m_target_window_type; - - cerr<<"loading window type session "<m_length!=block_size) { if (m_fftw == NULL) delete m_fftw; - m_fftw = new FFT(block_size,100); + m_fftw = new FFT(block_size,rate,100); if (m_mfcc_proc == NULL) delete m_mfcc_proc; - m_mfcc_proc = new Aquila::Mfcc(block_size); + m_mfcc_proc = new Aquila::Mfcc(block_size,rate); } } diff --git a/brain/src/block.h b/brain/src/block.h index a10858f..3aae3a2 100644 --- a/brain/src/block.h +++ b/brain/src/block.h @@ -38,7 +38,7 @@ namespace spiralcore { // returns distance based on ratio of fft-mfcc values double compare(const block &other, const search_params ¶ms) const; - static void init_fft(u32 block_size); + static void init_fft(u32 block_size, u32 rate); static bool unit_test(); const sample &get_pcm() const { return m_pcm; } diff --git a/brain/src/brain.cpp b/brain/src/brain.cpp index b57db1b..6c63289 100644 --- a/brain/src/brain.cpp +++ b/brain/src/brain.cpp @@ -377,34 +377,6 @@ void brain::deplete_usage() { } } - -// take another brain and rebuild this brain from bits of that one -// (presumably this one is made from a single sample) -/*void brain::resynth(const string &filename, const brain &other, const search_params ¶ms){ - sample out((m_block_size-m_overlap)*m_blocks.size()); - out.zero(); - u32 pos = 0; - u32 count = 0; - cerr<::iterator i=m_blocks.begin(); i!=m_blocks.end(); ++i) { - cerr<<'\r'; - cerr<<"searching: "<fft(source); - Aquila::MelFilterBank bank(44100, m_inputSize); + Aquila::MelFilterBank bank(m_sampleRate, m_inputSize); auto filterOutput = bank.applyAll(spectrum); Aquila::Dct dct; diff --git a/brain/src/mfcc.h b/brain/src/mfcc.h index 03b2c4b..c203d5a 100644 --- a/brain/src/mfcc.h +++ b/brain/src/mfcc.h @@ -59,8 +59,9 @@ namespace Aquila * * @param inputSize input length (common to all inputs) */ - Mfcc(std::size_t inputSize): - m_inputSize(inputSize)//, m_fft(FftFactory::getFft(inputSize)) + Mfcc(std::size_t inputSize, unsigned int sampleRate): + m_inputSize(inputSize), // m_fft(FftFactory::getFft(inputSize)) + m_sampleRate(sampleRate) { } @@ -71,7 +72,8 @@ namespace Aquila * Number of samples in each processed input. */ const std::size_t m_inputSize; - + const unsigned int m_sampleRate; + /** * FFT calculator. */ diff --git a/brain/src/spiralcore/audio.cpp b/brain/src/spiralcore/audio.cpp index e108e31..74a64b7 100644 --- a/brain/src/spiralcore/audio.cpp +++ b/brain/src/spiralcore/audio.cpp @@ -27,7 +27,8 @@ audio_device::audio_device(const string &clientname, u32 samplerate, u32 buffer_ left_in(buffer_size), right_in(buffer_size), m_recording(false), - m_record_filename("") + m_record_filename(""), + m_samplerate(samplerate) { portaudio_client::device_options opt; opt.buffer_size = buffer_size; @@ -46,7 +47,7 @@ void audio_device::save_sample(const string &filename, const sample s) { SF_INFO sfinfo; sfinfo.format=SF_FORMAT_WAV | SF_FORMAT_FLOAT; sfinfo.frames=s.get_length(); - sfinfo.samplerate=44100; + sfinfo.samplerate=m_samplerate; sfinfo.channels=1; sfinfo.sections=1; sfinfo.seekable=0; diff --git a/brain/src/spiralcore/audio.h b/brain/src/spiralcore/audio.h index 9a73582..b3e0adf 100644 --- a/brain/src/spiralcore/audio.h +++ b/brain/src/spiralcore/audio.h @@ -24,8 +24,8 @@ class graph; namespace spiralcore { -class audio_device { -public: + class audio_device { + public: audio_device(const string &clientname, u32 samplerate, u32 buffer_size); void start_graph(graph *graph); @@ -42,15 +42,15 @@ public: portaudio_client m_client; - static void save_sample(const std::string &filename, const sample s); + void save_sample(const std::string &filename, const sample s); -private: + private: bool m_recording; std::string m_record_filename; sample m_record_buffer_left; sample m_record_buffer_right; u32 m_record_counter; - -}; + u32 m_samplerate; + }; } diff --git a/brain/src/spiralcore/portaudio_client.cpp b/brain/src/spiralcore/portaudio_client.cpp index a989474..ed59ebd 100644 --- a/brain/src/spiralcore/portaudio_client.cpp +++ b/brain/src/spiralcore/portaudio_client.cpp @@ -21,7 +21,6 @@ bool portaudio_client::m_attached = false; long unsigned int portaudio_client::m_buffer_size = 0; -long unsigned int portaudio_client::m_sample_rate = 44100; void (*portaudio_client::run_callback)(void*, unsigned int buf_size)=NULL; void *portaudio_client::run_context = NULL; const float *portaudio_client::m_right_data=NULL; @@ -42,6 +41,23 @@ portaudio_client::~portaudio_client() { ///////////////////////////////////////////////////////////////////////////////////////////// +vector portaudio_client::sniff_devices() { + vector ret; + int numDevices = Pa_GetDeviceCount(); + if(numDevices < 0) { + // this is an error I guess + return ret; + } + const PaDeviceInfo *deviceInfo; + for(int i=0; iname); + } + return ret; +} + +///////////////////////////////////////////////////////////////////////////////////////////// + bool portaudio_client::attach(const string &client_name, const device_options &dopt) { if (m_attached) return true; diff --git a/brain/src/spiralcore/portaudio_client.h b/brain/src/spiralcore/portaudio_client.h index 8093c4f..7fcc2c9 100644 --- a/brain/src/spiralcore/portaudio_client.h +++ b/brain/src/spiralcore/portaudio_client.h @@ -42,6 +42,7 @@ class portaudio_client unsigned int out_channels; }; + vector sniff_devices(); bool attach(const string &client_name, const device_options &dopt); void detach(); bool is_attached() { return m_attached; } @@ -60,7 +61,6 @@ class portaudio_client private: static long unsigned int m_buffer_size; - static long unsigned int m_sample_rate; static bool m_attached; static const float *m_right_data; diff --git a/brain/src/status.cpp b/brain/src/status.cpp index 4fc07ec..86c61b2 100644 --- a/brain/src/status.cpp +++ b/brain/src/status.cpp @@ -34,6 +34,10 @@ void status::sound_item_refresh() { lo_send(m_address,"/sound-item-refresh",""); } +void status::add_audio_device(const std::string &name) { + lo_send(m_address,"/add_audio_device",name.c_str()); +} + void status::update(const char *msg, ...) { va_list args; va_start(args, msg); diff --git a/brain/src/status.h b/brain/src/status.h index 4a78a1e..f3da884 100644 --- a/brain/src/status.h +++ b/brain/src/status.h @@ -24,11 +24,12 @@ namespace spiralcore { class status { public: - static void _update(const std::string &msg); - static void update(const char *msg, ...); - static void sound_item(const std::string &name, const std::string &colour); - static void sound_item_refresh(); - static lo_address m_address; + static void _update(const std::string &msg); + static void update(const char *msg, ...); + static void sound_item(const std::string &name, const std::string &colour); + static void sound_item_refresh(); + static void add_audio_device(const std::string &name); + static lo_address m_address; }; } From 729edb25cd082519666ce95b99d2f2bedc25f897 Mon Sep 17 00:00:00 2001 From: Diemo Date: Sat, 1 Oct 2022 09:08:00 +0000 Subject: [PATCH 15/50] update url of catart in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e2ea65d..03688d0 100644 --- a/README.md +++ b/README.md @@ -166,4 +166,4 @@ Written by [Dave Griffiths at Then Try This](http://thentrythis.org). ## Links -To find related tech like [CataRT](http://imtr.ircam.fr/imtr/CataRT), [bbcut2](https://composerprogrammer.com/bbcut2.html), [eargram](https://sites.google.com/site/eargram/) and [sCrAmBlEd?HaCkZ!](https://www.youtube.com/watch?v=eRlhKaxcKpA) search up [granular synthesis](http://granularsynthesis.com/guide.php), [concatenative synthesis](https://hal.archives-ouvertes.fr/hal-01161337), [neural audio synthesis](https://github.com/acids-ircam/RAVE), [sinewave speech](http://www.lifesci.sussex.ac.uk/home/Chris_Darwin/SWS/), automated breakbeat cutting, audio mosaicing and plunderphonics/plundermatics. +To find related tech like [CataRT](https://ircam-ismm.github.io/max-msp/catart.html), [bbcut2](https://composerprogrammer.com/bbcut2.html), [eargram](https://sites.google.com/site/eargram/) and [sCrAmBlEd?HaCkZ!](https://www.youtube.com/watch?v=eRlhKaxcKpA) search up [granular synthesis](http://granularsynthesis.com/guide.php), [concatenative synthesis](https://hal.archives-ouvertes.fr/hal-01161337), [neural audio synthesis](https://github.com/acids-ircam/RAVE), [sinewave speech](http://www.lifesci.sussex.ac.uk/home/Chris_Darwin/SWS/), automated breakbeat cutting, audio mosaicing and plunderphonics/plundermatics. From 4a4ab8b41a5aa563a981f71791dc14469d21d30c Mon Sep 17 00:00:00 2001 From: Dave Griffiths Date: Mon, 3 Oct 2022 09:56:25 +0100 Subject: [PATCH 16/50] added audio settings options for selection of device, samplrate and buffer size, also fixed: #65 & fixed: #64 --- app/MainWindow.cpp | 9 +- app/MainWindow.h | 104 ++++--- app/SettingsDialog.cpp | 39 +++ app/SettingsDialog.h | 75 ++++++ app/audio_thread.cpp | 30 ++- app/audio_thread.h | 5 +- app/feedback.cpp | 3 +- app/feedback.h | 7 +- app/process_thread.cpp | 4 +- app/qtmain.cpp | 5 +- brain/src/spiralcore/audio.cpp | 41 +-- brain/src/spiralcore/audio.h | 4 +- brain/src/spiralcore/portaudio_client.cpp | 174 +++++++----- brain/src/spiralcore/portaudio_client.h | 32 ++- brain/src/status.cpp | 4 - brain/src/status.h | 3 +- gui/samplebrain.ui | 281 ++++++++++--------- gui/settings.ui | 314 ++++++++++++++++++++++ samplebrain.pro | 7 +- 19 files changed, 859 insertions(+), 282 deletions(-) create mode 100644 app/SettingsDialog.cpp create mode 100644 app/SettingsDialog.h create mode 100644 gui/settings.ui diff --git a/app/MainWindow.cpp b/app/MainWindow.cpp index 850d0de..8a0555f 100644 --- a/app/MainWindow.cpp +++ b/app/MainWindow.cpp @@ -14,6 +14,9 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +#ifndef MAIN_WINDOW +#define MAIN_WINDOW + #include #include #include @@ -45,7 +48,9 @@ MainWindow::MainWindow() : m_Ui.brain_contents->setAlignment(Qt::AlignTop); m_Ui.brain_contents->setSpacing(0); m_Ui.brain_contents->setContentsMargins(0,0,0,0); - + + m_settings_dialog = new SettingsDialog(this); + // add default local dest // turn on first one @@ -183,3 +188,5 @@ void MainWindow::init_from_session(const string &filename) { } + +#endif diff --git a/app/MainWindow.h b/app/MainWindow.h index 90abd9d..14d05c4 100644 --- a/app/MainWindow.h +++ b/app/MainWindow.h @@ -19,6 +19,7 @@ #include #include #include "ui_samplebrain.h" +#include "SettingsDialog.h" #include #include @@ -26,6 +27,7 @@ #include #include "feedback.h" #include "sound_items.h" +#include "audio_thread.h" using namespace std; using namespace spiralcore; @@ -37,7 +39,65 @@ class MainWindow : public QMainWindow public: MainWindow(); + // all this to work around liblo's use of varargs... + void send_audio_osc(const char *name, const char *types) { + for (auto dest:m_destinations) { + if (dest.m_enabled) { + lo_send(dest.m_audio_address,name,types); + } + } + } + template + void send_audio_osc(const char *name, const char *types, T val) { + for (auto dest:m_destinations) { + if (dest.m_enabled) { + lo_send(dest.m_audio_address,name,types,val); + } + } + } + + void send_process_osc(const char *name, const char *types) { + for (auto dest:m_destinations) { + if (dest.m_enabled) { + lo_send(dest.m_process_address,name,types); + } + } + } + + template + void send_process_osc(const char *name, const char *types, T val) { + for (auto dest:m_destinations) { + if (dest.m_enabled) { + lo_send(dest.m_process_address,name,types,val); + } + } + } + + void set_audio_thread(audio_thread *at) { + m_audio_thread=at; + m_audio_thread->start_audio(); + + for (auto &d:m_audio_thread->m_audio_device->m_client.m_devices) { + if (d.id==0) { + m_settings_dialog->m_Ui.deviceComboBox->clear(); + } + m_settings_dialog->m_Ui.deviceComboBox->addItem(d.name.c_str()); + + if (d.default_output==1) { + m_settings_dialog->m_device=d.name; + m_settings_dialog->m_Ui.deviceComboBox->setCurrentText(d.name.c_str()); + } + + } + + m_settings_dialog->m_Ui.messagesLabel->setText(m_audio_thread->m_audio_device->m_client.m_status.c_str()); + } + + audio_thread *get_audio_thread() { + return m_audio_thread; + } + protected: private slots: @@ -310,7 +370,7 @@ private slots: } void update_status() { - m_feedback.poll(m_Ui.statusbar,&m_sound_items); + m_feedback.poll(m_Ui.statusbar,&m_sound_items,m_settings_dialog); } void stereo_mode(bool s) { @@ -336,7 +396,10 @@ private slots: } - + void settings() { + m_settings_dialog->show(); + } + private: /////////////////////////////////////////////// @@ -362,41 +425,6 @@ private: vector m_destinations; - // all this to work around liblo's use of varargs... - void send_audio_osc(const char *name, const char *types) { - for (auto dest:m_destinations) { - if (dest.m_enabled) { - lo_send(dest.m_audio_address,name,types); - } - } - } - - template - void send_audio_osc(const char *name, const char *types, T val) { - for (auto dest:m_destinations) { - if (dest.m_enabled) { - lo_send(dest.m_audio_address,name,types,val); - } - } - } - - void send_process_osc(const char *name, const char *types) { - for (auto dest:m_destinations) { - if (dest.m_enabled) { - lo_send(dest.m_process_address,name,types); - } - } - } - - template - void send_process_osc(const char *name, const char *types, T val) { - for (auto dest:m_destinations) { - if (dest.m_enabled) { - lo_send(dest.m_process_address,name,types,val); - } - } - } - void init_from_session(const string &filename); void add_gui_address(osc_destination &dest, @@ -411,5 +439,7 @@ private: QSignalMapper* m_sound_item_delete_mapper; sound_items m_sound_items; + SettingsDialog *m_settings_dialog; + audio_thread *m_audio_thread; }; diff --git a/app/SettingsDialog.cpp b/app/SettingsDialog.cpp new file mode 100644 index 0000000..a2edf7b --- /dev/null +++ b/app/SettingsDialog.cpp @@ -0,0 +1,39 @@ +// Copyright (C) 2022 Then Try This +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include +#include +#include + +#include "MainWindow.h" +#include "SettingsDialog.h" +#include "feedback.h" + +using namespace std; + +SettingsDialog::SettingsDialog(MainWindow *parent): + m_device(""), + m_parent(parent), + m_buffersize(2048), + m_samplerate(44100) { + m_Ui.setupUi(this); +} + +void SettingsDialog::connect() { + audio_thread *at = m_parent->get_audio_thread(); + at->restart_audio(m_device,m_samplerate,m_buffersize); + m_Ui.messagesLabel->setText(at->m_audio_device->m_client.m_status.c_str()); +} diff --git a/app/SettingsDialog.h b/app/SettingsDialog.h new file mode 100644 index 0000000..df5248f --- /dev/null +++ b/app/SettingsDialog.h @@ -0,0 +1,75 @@ +// Copyright (C) 2022 Then Try This +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef SETTINGS_DIALOG +#define SETTINGS_DIALOG + +#include +#include +#include +#include "ui_settings.h" + +#include +#include +#include +#include + +using namespace std; + + +class MainWindow; + +class SettingsDialog : public QDialog +{ + Q_OBJECT + public: + SettingsDialog(MainWindow *parent); + + Ui_SettingsDialog m_Ui; + string m_device; + + protected: + + private slots: + + void samplerate(QString str) { + try { m_samplerate=stoi(str.toStdString()); } + catch(std::exception const & e) { + m_samplerate=44100; + } + } + void output_device(QString v) { m_device=v.toStdString(); } + void buffersize(QString str) { + try { m_buffersize=stoi(str.toStdString()); } + catch(std::exception const & e) { + m_buffersize=44100; + } + } + void accept() { connect(); hide(); } + void reject() { hide(); } + void apply() { connect(); } + + private: + + void connect(); + MainWindow *m_parent; + + unsigned int m_buffersize; + unsigned int m_samplerate; + +}; + +#endif diff --git a/app/audio_thread.cpp b/app/audio_thread.cpp index a6307b4..1d51bcf 100644 --- a/app/audio_thread.cpp +++ b/app/audio_thread.cpp @@ -28,15 +28,18 @@ audio_thread::audio_thread(process_thread &p) : m_stereo_mode(false), m_mic_mode(false), m_bufsize(2048), - m_samplerate(44100) + m_samplerate(44100), + m_device("") { - start_audio(); + // start_audio(); pthread_mutex_lock(m_brain_mutex); m_left_renderer = new renderer(p.m_source,p.m_left_target); m_right_renderer = new renderer(p.m_source,p.m_right_target); m_block_stream = new block_stream(); pthread_mutex_unlock(m_brain_mutex); m_osc.run(); + // it this threadsafe? + // m_audio_device->report_devices(); } static bool state = 1; @@ -54,13 +57,24 @@ void audio_thread::start_audio() { m_audio_device->m_client.set_callback(run_audio, this); } +void audio_thread::restart_audio(const string device, unsigned int samplerate, unsigned int bufsize) { + m_samplerate = samplerate; + m_bufsize = bufsize; + m_device = device; + m_audio_device->connect(m_device, + "samplebrain", + m_samplerate, + m_bufsize); +} + + void audio_thread::run_audio(void* c, unsigned int frames) { if (state) { audio_thread *at = (audio_thread*)c; at->m_audio_device->left_out.zero(); at->process(at->m_audio_device->left_in, - at->m_audio_device->right_in, - at->m_audio_device->left_out, + at->m_audio_device->right_in, + at->m_audio_device->left_out, at->m_audio_device->right_out); at->m_audio_device->maybe_record(); } @@ -187,12 +201,12 @@ void audio_thread::process(sample &left_in, sample &right_in, sample &left_out, } m_left_renderer->process(left_out.get_length(), - left_out.get_non_const_buffer(), - bs); + left_out.get_non_const_buffer(), + bs); if (m_stereo_mode) { m_right_renderer->process(right_out.get_length(), - right_out.get_non_const_buffer(), - bs); + right_out.get_non_const_buffer(), + bs); } else { right_out=left_out; } diff --git a/app/audio_thread.h b/app/audio_thread.h index 0c00911..bcee447 100644 --- a/app/audio_thread.h +++ b/app/audio_thread.h @@ -29,6 +29,9 @@ namespace spiralcore { audio_thread(process_thread &p); ~audio_thread(); + void start_audio(); + void restart_audio(const string device, unsigned int samplerate, unsigned int bufsize); + void process(sample &left_in, sample &right_in, sample &left_out, sample &right_out); static void run_audio(void* c, unsigned int frames); @@ -39,7 +42,6 @@ namespace spiralcore { block_stream *m_block_stream; private: - void start_audio(); OSC_server m_osc; process_thread &m_process_thread; @@ -48,6 +50,7 @@ namespace spiralcore { bool m_mic_mode; u32 m_bufsize; u32 m_samplerate; + string m_device; }; } diff --git a/app/feedback.cpp b/app/feedback.cpp index 30d4887..582329c 100644 --- a/app/feedback.cpp +++ b/app/feedback.cpp @@ -16,6 +16,7 @@ #include "feedback.h" #include "sound_items.h" +#include "SettingsDialog.h" #include using namespace spiralcore; @@ -28,7 +29,7 @@ feedback::feedback(string address) : } -void feedback::poll(QStatusBar *s, sound_items *sound_items) { +void feedback::poll(QStatusBar *s, sound_items *sound_items, SettingsDialog *settings) { command_ring_buffer::command cmd; while (m_osc.get(cmd)) { diff --git a/app/feedback.h b/app/feedback.h index 9638f91..4de48d8 100644 --- a/app/feedback.h +++ b/app/feedback.h @@ -17,6 +17,7 @@ #include #include #include +#include "SettingsDialog.h" #include "spiralcore/OSC_server.h" #pragma once @@ -26,12 +27,12 @@ class sound_items; class feedback { public: - feedback(std::string address); - void poll(QStatusBar *s, sound_items *sound_items); + feedback(std::string address); + void poll(QStatusBar *s, sound_items *sound_items, SettingsDialog *settings); private: - OSC_server m_osc; + OSC_server m_osc; }; } diff --git a/app/process_thread.cpp b/app/process_thread.cpp index fec26f2..8208b5b 100644 --- a/app/process_thread.cpp +++ b/app/process_thread.cpp @@ -34,9 +34,9 @@ static void* _process(void *c) { process_thread::process_thread() : m_osc("8889"), - m_source_block_size(3000), + m_source_block_size(1000), m_source_overlap(0.75), - m_target_block_size(3000), + m_target_block_size(1000), m_target_overlap(0.75), m_window_type(window::DODGY), m_target_window_type(window::DODGY) diff --git a/app/qtmain.cpp b/app/qtmain.cpp index f734c68..c505db3 100644 --- a/app/qtmain.cpp +++ b/app/qtmain.cpp @@ -30,12 +30,11 @@ int main( int argc , char *argv[] ){ QApplication app(argc, argv); MainWindow mainWin; mainWin.show(); - - cerr<<"Qt version: "<maxOutputChannels>=2) { + device_desc desc; + desc.name = deviceInfo->name; + desc.id = i; + desc.default_input = i==default_input_num; + desc.default_output = i==default_output_num; + m_devices.push_back(desc); + } + } + + m_initialised=true; + return true; + } + return true; +} + +int portaudio_client::device_name_to_id(const string &name) { + for (auto &d:m_devices) { + if (d.name==name) return d.id; + } + return -1; +} + +///////////////////////////////////////////////////////////////////////////////////////////// + +bool portaudio_client::attach(const string &requested_output_device, const string &client_name, const device_options &dopt) { + if (!init()) return false; detach(); -} - -///////////////////////////////////////////////////////////////////////////////////////////// - -vector portaudio_client::sniff_devices() { - vector ret; - int numDevices = Pa_GetDeviceCount(); - if(numDevices < 0) { - // this is an error I guess - return ret; - } - const PaDeviceInfo *deviceInfo; - for(int i=0; iname); - } - return ret; -} - -///////////////////////////////////////////////////////////////////////////////////////////// - -bool portaudio_client::attach(const string &client_name, const device_options &dopt) { - if (m_attached) return true; - - PaError err; - err = Pa_Initialize(); - if( err != paNoError ) { - cerr<<"could not init portaudio_client"<defaultLowOutputLatency; - output_parameters.hostApiSpecificStreamInfo = NULL; - cerr<<"Connecting to "<name<<" for output"<defaultLowOutputLatency; + output_parameters.hostApiSpecificStreamInfo = NULL; + m_status="connecting to "+string(Pa_GetDeviceInfo(output_parameters.device)->name)+" for output\n"; + + // turn off input for the moment, it's causing + // too many cross platform security issues + + /*PaStreamParameters input_parameters; PaStreamParameters *input_p=&input_parameters; input_parameters.device = input_device_num; if (true || input_parameters.device == paNoDevice) { cerr<<"error: no default input device."<defaultLowInputLatency; input_parameters.hostApiSpecificStreamInfo = NULL; cerr<<"Connecting to "<name<<" for input"< sniff_devices(); - bool attach(const string &client_name, const device_options &dopt); + class device_desc { + public: + string name; + int id; + bool default_input; + bool default_output; + }; + + vector m_devices; + + bool attach(const string &requested_output_device, const string &client_name, const device_options &dopt); void detach(); - bool is_attached() { return m_attached; } + bool is_attached() { return m_attached_device!=-1; } void set_callback(void(*run)(void*, unsigned int),void *context) { run_callback=run; run_context=context; } void set_outputs(const float *l, const float *r) { m_left_data=l; m_right_data=r; } void set_inputs(float *l, float *r) { m_left_in_data=l; m_right_in_data=r; } + string m_status; + protected: static int process(const void *input_buffer, void *output_buffer, @@ -60,16 +71,21 @@ class portaudio_client private: + int device_name_to_id(const string &name); + static long unsigned int m_buffer_size; - static bool m_attached; - + static bool m_initialised; + static int m_attached_device; + static const float *m_right_data; static const float *m_left_data; static float *m_right_in_data; static float *m_left_in_data; - + static void(*run_callback)(void *, unsigned int); static void *run_context; + static PaStream *m_stream; + }; #endif diff --git a/brain/src/status.cpp b/brain/src/status.cpp index 86c61b2..4fc07ec 100644 --- a/brain/src/status.cpp +++ b/brain/src/status.cpp @@ -34,10 +34,6 @@ void status::sound_item_refresh() { lo_send(m_address,"/sound-item-refresh",""); } -void status::add_audio_device(const std::string &name) { - lo_send(m_address,"/add_audio_device",name.c_str()); -} - void status::update(const char *msg, ...) { va_list args; va_start(args, msg); diff --git a/brain/src/status.h b/brain/src/status.h index f3da884..0add04f 100644 --- a/brain/src/status.h +++ b/brain/src/status.h @@ -28,7 +28,8 @@ public: static void update(const char *msg, ...); static void sound_item(const std::string &name, const std::string &colour); static void sound_item_refresh(); - static void add_audio_device(const std::string &name); + static void add_audio_device(int id, const std::string &name, bool default_output); + static void audio_device_status(const std::string &status); static lo_address m_address; }; diff --git a/gui/samplebrain.ui b/gui/samplebrain.ui index fc4d160..e5d802a 100644 --- a/gui/samplebrain.ui +++ b/gui/samplebrain.ui @@ -403,7 +403,7 @@ - how long it takes for the novelty to wear off + likelihood of playing the next block rather than the closest 100 @@ -752,7 +752,7 @@ 99999 - 3000 + 1000 @@ -783,7 +783,7 @@ 0.010000000000000 - 0.800000000000000 + 0.750000000000000 @@ -1236,7 +1236,7 @@ 99999 - 3000 + 1000 @@ -1267,7 +1267,7 @@ 0.010000000000000 - 0.000000000000000 + 0.750000000000000 @@ -1562,6 +1562,20 @@ + + + + + Comic Sans MS + 75 + true + + + + settings + + + @@ -1604,8 +1618,8 @@ play_slot() - 64 - 62 + 78 + 830 399 @@ -1620,8 +1634,8 @@ stop_slot() - 155 - 62 + 180 + 830 399 @@ -1636,8 +1650,8 @@ volume_slot(int) - 189 - 480 + 465 + 852 361 @@ -1652,8 +1666,8 @@ stop_record() - 328 - 543 + 356 + 840 361 @@ -1668,8 +1682,8 @@ record() - 236 - 543 + 268 + 840 361 @@ -1684,8 +1698,8 @@ fft1_end_slot(int) - 154 - 310 + 421 + 293 399 @@ -1700,8 +1714,8 @@ ratio_slot(double) - 109 - 223 + 421 + 185 399 @@ -1716,12 +1730,12 @@ setValue(int) - 237 - 289 + 319 + 603 - 341 - 289 + 421 + 614 @@ -1732,8 +1746,8 @@ target_mix_slot(double) - 330 - 446 + 720 + 669 361 @@ -1748,8 +1762,8 @@ generate_target_blocks() - 277 - 202 + 670 + 395 399 @@ -1764,8 +1778,8 @@ target_block_size(int) - 313 - 136 + 720 + 237 399 @@ -1780,8 +1794,8 @@ load_target() - 277 - 103 + 670 + 184 399 @@ -1796,8 +1810,8 @@ fft1_start_slot(int) - 154 - 277 + 298 + 293 399 @@ -1812,8 +1826,8 @@ target_block_overlap(double) - 619 - 196 + 720 + 291 361 @@ -1828,12 +1842,12 @@ setValue(int) - 341 - 289 + 421 + 614 - 237 - 289 + 319 + 603 @@ -1844,8 +1858,8 @@ synapses(int) - 341 - 289 + 421 + 614 566 @@ -1860,8 +1874,8 @@ ratio_slot(int) - 189 - 141 + 323 + 174 361 @@ -1876,8 +1890,8 @@ n_ratio_slot(int) - 159 - 211 + 323 + 228 361 @@ -1892,8 +1906,8 @@ n_ratio_slot(double) - 330 - 211 + 421 + 239 361 @@ -1908,8 +1922,8 @@ target_mix_slot(int) - 159 - 446 + 622 + 658 361 @@ -1924,8 +1938,8 @@ n_mix_slot(int) - 159 - 386 + 622 + 604 361 @@ -1940,8 +1954,8 @@ n_mix_slot(double) - 330 - 386 + 720 + 615 361 @@ -1956,8 +1970,8 @@ load_brain() - 615 - 357 + 941 + 661 566 @@ -1972,8 +1986,8 @@ boredom_slot(double) - 411 - 253 + 421 + 401 321 @@ -1988,8 +2002,8 @@ novelty_slot(double) - 411 - 217 + 421 + 347 321 @@ -2004,8 +2018,8 @@ novelty_slot(int) - 291 - 217 + 323 + 336 321 @@ -2020,8 +2034,8 @@ boredom_slot(int) - 291 - 253 + 323 + 390 321 @@ -2036,8 +2050,8 @@ synapses(int) - 237 - 289 + 319 + 603 566 @@ -2052,8 +2066,8 @@ block_size(int) - 728 - 109 + 1157 + 450 566 @@ -2068,8 +2082,8 @@ block_overlap(double) - 728 - 145 + 1157 + 504 566 @@ -2084,8 +2098,8 @@ save_brain() - 728 - 357 + 1157 + 661 566 @@ -2100,8 +2114,8 @@ clear_brain() - 1061 - 550 + 1157 + 396 566 @@ -2116,8 +2130,8 @@ generate() - 671 - 322 + 1158 + 608 566 @@ -2132,8 +2146,8 @@ load_sound() - 841 - 550 + 868 + 396 566 @@ -2148,12 +2162,12 @@ setValue(int) - 241 - 282 + 346 + 498 - 343 - 282 + 421 + 509 @@ -2164,12 +2178,12 @@ setValue(int) - 343 - 282 + 421 + 509 - 241 - 282 + 346 + 498 @@ -2180,8 +2194,8 @@ search_stretch(int) - 241 - 282 + 346 + 498 566 @@ -2196,12 +2210,12 @@ setValue(int) - 231 - 410 + 305 + 657 - 337 - 410 + 421 + 668 @@ -2212,12 +2226,12 @@ setValue(int) - 337 - 410 + 421 + 668 - 231 - 410 + 305 + 657 @@ -2228,8 +2242,8 @@ slide_error(int) - 231 - 410 + 305 + 657 566 @@ -2244,8 +2258,8 @@ stickyness_slot(double) - 385 - 296 + 421 + 455 609 @@ -2260,8 +2274,8 @@ stickyness_slot(int) - 266 - 296 + 323 + 444 609 @@ -2276,8 +2290,8 @@ autotune(int) - 531 - 477 + 622 + 550 454 @@ -2292,8 +2306,8 @@ autotune(double) - 616 - 477 + 720 + 561 454 @@ -2308,8 +2322,8 @@ load_session() - 535 - 707 + 616 + 799 454 @@ -2324,8 +2338,8 @@ save_session() - 535 - 744 + 616 + 851 454 @@ -2340,8 +2354,8 @@ stereo_mode(bool) - 539 - 594 + 721 + 717 454 @@ -2356,8 +2370,8 @@ algo(int) - 273 - 395 + 421 + 560 454 @@ -2372,8 +2386,8 @@ target_shape(int) - 557 - 233 + 720 + 342 454 @@ -2388,8 +2402,8 @@ brain_shape(int) - 824 - 432 + 1157 + 555 454 @@ -2404,8 +2418,8 @@ load_sounds() - 754 - 321 + 1013 + 396 454 @@ -2420,8 +2434,8 @@ select_all() - 813 - 74 + 1073 + 124 454 @@ -2436,8 +2450,8 @@ select_none() - 862 - 74 + 1157 + 124 454 @@ -2452,8 +2466,8 @@ mic(bool) - 404 - 601 + 721 + 442 454 @@ -2461,6 +2475,22 @@ + + pushButton + clicked() + MainWindow + settings() + + + 680 + 809 + + + 776 + 849 + + + play_slot() @@ -2518,5 +2548,6 @@ select_all() select_none() mic(bool) + settings() diff --git a/gui/settings.ui b/gui/settings.ui new file mode 100644 index 0000000..b530397 --- /dev/null +++ b/gui/settings.ui @@ -0,0 +1,314 @@ + + + SettingsDialog + + + + 0 + 0 + 400 + 522 + + + + settings + + + + + + + + + + + Comic Sans MS + + + + device + + + + + + + + + + + + + + + Comic Sans MS + + + + sample rate + + + + + + + + 0 + 0 + + + + 44100 + + + + + + + + + + Comic Sans MS + 8 + + + + (note: this currently should probably match your sample file's input rate as no conversion is run on them - yet) + + + true + + + + + + + + + + + + Comic Sans MS + + + + buffer size + + + + + + + 5 + + + + 64 + + + + + 128 + + + + + 256 + + + + + 512 + + + + + 1024 + + + + + 2048 + + + + + 4096 + + + + + + + + + + + Comic Sans MS + + + + messages + + + + + + + + 0 + 0 + + + + + Comic Sans MS + 8 + + + + false + + + border: 1px solid; + + + + + + + + + + + Comic Sans MS + + + + apply settings + + + + + + + + Comic Sans MS + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + SettingsDialog + accept() + + + 231 + 358 + + + 157 + 274 + + + + + buttonBox + rejected() + SettingsDialog + reject() + + + 299 + 364 + + + 286 + 274 + + + + + samplerateLineEdit + textEdited(QString) + SettingsDialog + samplerate(QString) + + + 270 + 71 + + + 394 + 156 + + + + + applyPushButton + clicked() + SettingsDialog + apply() + + + 112 + 321 + + + 4 + 315 + + + + + deviceComboBox + currentTextChanged(QString) + SettingsDialog + output_device(QString) + + + 323 + 45 + + + 393 + 64 + + + + + buffersizeComboBox + currentTextChanged(QString) + SettingsDialog + buffersize(QString) + + + 326 + 131 + + + 395 + 208 + + + + + + output_device(QString) + samplerate(QString) + buffersize(QString) + apply() + + diff --git a/samplebrain.pro b/samplebrain.pro index 22799fb..8b047df 100644 --- a/samplebrain.pro +++ b/samplebrain.pro @@ -10,11 +10,14 @@ INCLUDEPATH += . 2 QT += core gui widgets # Input -HEADERS += app/MainWindow.h +HEADERS += app/MainWindow.h \ + app/SettingsDialog.h -FORMS += gui/samplebrain.ui +FORMS += gui/samplebrain.ui \ + gui/settings.ui SOURCES += app/MainWindow.cpp \ + app/SettingsDialog.cpp \ app/sound_items.cpp \ app/audio_thread.cpp \ app/process_thread.cpp \ From 014becd12f6737baa536440a612dbd8b4947c0d5 Mon Sep 17 00:00:00 2001 From: Dave Griffiths Date: Tue, 4 Oct 2022 10:33:11 +0100 Subject: [PATCH 17/50] version bump and added settings cog icon --- app/images/settings.png | Bin 0 -> 8528 bytes app/samplebrain.qrc | 1 + gui/samplebrain.ui | 19 +++++++++++++++---- 3 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 app/images/settings.png diff --git a/app/images/settings.png b/app/images/settings.png new file mode 100644 index 0000000000000000000000000000000000000000..705fc79b2301f55c247d328a1c984546a6ca618c GIT binary patch literal 8528 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 KMNUMnLSTY~Q!k + images/settings.png images/at.png images/pause.png images/play.png diff --git a/gui/samplebrain.ui b/gui/samplebrain.ui index e5d802a..4329661 100644 --- a/gui/samplebrain.ui +++ b/gui/samplebrain.ui @@ -11,7 +11,7 @@ - samplebrain 0.18.3 + samplebrain 0.18.4 @@ -1572,7 +1572,20 @@ - settings + + + + + :/images/images/settings.png:/images/images/settings.png + + + + 32 + 32 + + + + true @@ -1607,8 +1620,6 @@ - - From ad2a70a8d63e601f9d82d6372417f97ec3953dcc Mon Sep 17 00:00:00 2001 From: Claude Heiland-Allen Date: Tue, 4 Oct 2022 12:44:52 +0100 Subject: [PATCH 18/50] fix some tooltips in the GUI - some were incorrect, now corrected - some spinboxes were missing, now added (same as slider) - still not all widgets have tooltips (notably blocksize + overlap) --- gui/samplebrain.ui | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/gui/samplebrain.ui b/gui/samplebrain.ui index 4329661..a941b80 100644 --- a/gui/samplebrain.ui +++ b/gui/samplebrain.ui @@ -96,6 +96,9 @@ 0 + + plain fft match vs mfcc values + 1.000000000000000 @@ -156,6 +159,9 @@ 0 + + match original or normalised blocks + 1.000000000000000 @@ -298,6 +304,9 @@ 0 + + use new blocks rather than similar ones + 1.000000000000000 @@ -361,6 +370,9 @@ 0 + + how long it takes for the novelty to wear off + 1.000000000000000 @@ -424,6 +436,9 @@ 0 + + likelihood of playing the next block rather than the closest + 1.000000000000000 @@ -491,7 +506,7 @@ - how many connections to search (ordered in closeness) + repeat search (set novelty to 0 for dodgy jungle timessssstretch) 1 @@ -666,7 +681,7 @@ - how many connections to search (ordered in closeness) + block difference higher than this causes a new search, skipping the target 10000 @@ -944,6 +959,9 @@ 0 + + amount to match the frequency + 1.000000000000000 @@ -1001,6 +1019,9 @@ 0 + + mix in the normalised blocks + 1.000000000000000 @@ -1061,6 +1082,9 @@ 0 + + mix in the original blocks + 1.000000000000000 From b569c47d401314a44a2ee114bbff747495f888ef Mon Sep 17 00:00:00 2001 From: Dave Griffiths Date: Tue, 4 Oct 2022 13:55:12 +0100 Subject: [PATCH 19/50] soundcard text top aligned --- gui/settings.ui | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gui/settings.ui b/gui/settings.ui index b530397..9d740c0 100644 --- a/gui/settings.ui +++ b/gui/settings.ui @@ -175,6 +175,9 @@ + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + From 28a8bd31f857b29230e3282aacf2ccd4eb833b7c Mon Sep 17 00:00:00 2001 From: Claude Heiland-Allen Date: Wed, 5 Oct 2022 11:06:18 +0100 Subject: [PATCH 20/50] set dark foreground when using light background fixes hard-to-read light text on light background when using a dark system theme enabled for QT apps --- app/sound_items.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/sound_items.cpp b/app/sound_items.cpp index ce3704c..340c56c 100644 --- a/app/sound_items.cpp +++ b/app/sound_items.cpp @@ -33,7 +33,7 @@ sound_items::sound_item &sound_items::add(QVBoxLayout *container, const string & sound_item si; si.m_filename = name; si.m_id = m_current_sound_id++; - QString style("background-color:lightgrey;"); + QString style("color:black;background-color:lightgrey;"); si.m_container = new QHBoxLayout(); si.m_container->setSpacing(10); @@ -89,8 +89,8 @@ void sound_items::clear() { void sound_items::recolour() { u32 c=0; for (auto &si:m_sound_items) { - QString style("background-color:lightblue;"); - if (c%2==0) style="background-color:pink;"; + QString style("color:black;background-color:lightblue;"); + if (c%2==0) style="color:black;background-color:pink;"; si.m_enable->setStyleSheet(style); si.m_del->setStyleSheet(style); si.m_label->setStyleSheet(style); @@ -101,7 +101,7 @@ void sound_items::recolour() { void sound_items::change_colour(const std::string &name, const std::string &colour) { for (auto &si:m_sound_items) { if (si.m_filename==name) { - QString style("background-color:"+QString::fromStdString(colour)+";"); + QString style("color:black;background-color:"+QString::fromStdString(colour)+";"); si.m_enable->setStyleSheet(style); si.m_del->setStyleSheet(style); si.m_label->setStyleSheet(style); From 1c912d3977478a0f0576629cb17006eeb3066705 Mon Sep 17 00:00:00 2001 From: Dave Griffiths Date: Thu, 6 Oct 2022 20:43:06 +0100 Subject: [PATCH 21/50] readme tweaks --- README.md | 104 +++++++----------------------------------------------- 1 file changed, 12 insertions(+), 92 deletions(-) diff --git a/README.md b/README.md index 03688d0..70f928a 100644 --- a/README.md +++ b/README.md @@ -23,10 +23,9 @@ Quick start: 5. Press play 6. Tweak brain -The default block size (3000) is really high to prevent CPU glitches - -500 to 1000 is a better range. Larger wav files like whole tracks can -be used, but take a long time to process, after which they can be -saved as "brain" files and instantly reloaded. +Larger wav files like whole tracks can be used, but take a long time +to process, after which they can be saved as "brain" files and +instantly reloaded. # [Demo brain session](https://static.thentrythis.org/samplebrain/demo.samplebrain) @@ -47,11 +46,13 @@ written to run on a couple of computers!) you will have to bear with us as we gradually stabilise things based on your feedback. There might currently be problems running it on 64bit Windows. -* **Windows**: [samplebrain_0.18.3_win.zip](https://static.thentrythis.org/samplebrain/samplebrain_0.18.3_win.zip) -* **Mac (intel/m1)**: [samplebrain_0.18.3_macintel.zip](https://static.thentrythis.org/samplebrain/samplebrain_0.18.3_macintel.app.zip) +* **Windows**: [samplebrain_0.18.4_win.zip](https://static.thentrythis.org/samplebrain/samplebrain_0.18.4_win.zip) +* **Mac (intel/m1)**: [samplebrain_0.18.4_macintel.zip](https://static.thentrythis.org/samplebrain/samplebrain_0.18.4_macintel.app.zip) + +Changes in 0.18.4: New audio device settings window and updated +windows build. Better default block size, tool tip tweaks and fixes +for dark themes by [Claude Heiland-Allen](https://mathr.co.uk/). -(New 0.18.3 release fixes loading samples from paths longer than 255 characters) - Mac note: As this software is not on the apple store, to run the binary you need to tell your mac it's ok: Go to System Preferences > Security & Privacy > General. At the bottom of the window, select @@ -59,6 +60,8 @@ Security & Privacy > General. At the bottom of the window, select Thank you to [Nik Gaffney](http://fo.am) for help with the Apple builds +For old versions see the [changelog](changelog.md) + ### Linux Download on Flathub @@ -71,90 +74,7 @@ If you'd like the right font, optionally: $ sudo apt install ttf-mscorefonts-installer -# Old/broken/spurious binaries - -* **Windows**: [samplebrain_0.18.2_win.zip](https://static.thentrythis.org/samplebrain/samplebrain_0.18.2_win.zip) -* **Windows**: [samplebrain_0.18.1_win.zip](https://static.thentrythis.org/samplebrain/samplebrain_0.18.1_win.zip) -* **Windows**: [samplebrain_0.18_win.zip](https://static.thentrythis.org/samplebrain/samplebrain_0.18_win.zip) - -* **Mac (intel/m1)**: [samplebrain_0.18.1_macintel.zip](https://static.thentrythis.org/samplebrain/samplebrain_0.18.1_macintel.app.zip) -* **Mac (intel)**: [samplebrain_0.18_macintel.zip](https://static.thentrythis.org/samplebrain/samplebrain_0.18_macintel.zip) -* **Mac (m1)**: [samplebrain_0.18_m1_v2.dmg](https://static.thentrythis.org/samplebrain/samplebrain_0.18_m1_v2.dmg) - -# Building from source -## Linux (Ubuntu) -Install libraries for the sample engine (use brew on mac, MinGW on win): - - $ sudo apt install libsndfile1-dev portaudio19-dev liblo-dev libfftw3-dev - -Install dependencies for the interface: - - $ sudo apt install build-essential qtcreator qt5-default - -Build & run it: - - $ mkdir build - $ cd build - $ qmake .. - $ make - $ sudo make install - $ samplebrain - -## Mac -Install libraries for sample engine: - - $ brew install fftw portaudio liblo libsndfile - -Install dependencies for the interface: - - $ brew install qt - $ brew link qt - -Build & run it: - - $ mkdir build - $ cd build - $ qmake .. - $ make - -`samplebrain.app` should then be in the app folder for you to run. - -# Mac build additions - -To make a mac app bundle: - -Run `macdeployqt` which copies all dependencies inside the app. - - $ cd build - $ macdeployqt - -If the icon is not visible, you might need to copy desktop/samplebrain.icns (the icon) to the Resources directory in the app bundle. - - $ cp ../desktop/samplebrain.icns samplebrain.app/Contents/Resources - -Then edit Info.plist to add samplebrain.icns to CFBundleIconFile. Key `CFBundleIconFile` should match: - - CFBundleIconFile - samplebrain.icns - -You might also need to resign the app bundle after making any changes - - $ codesign --force --deep --sign - samplebrain.app - -## What's here - -1. brain: - * samplebrain engine code -2. app: - * code to build the Qt GUI app -3. gui: - * qt designer project files -4. desktop: - * various icon files etc -4. cooking: - * some sketches and ideas - * proof of concept written in python - * brief initial (abandoned) attempt at clojure version +# [Building from source](building.md) MFCC algo courtesy of the Aquila library by Zbigniew Siciarz MIT/X11 licence 2007-2014 (see brain/src/aquila/LICENCE) From a30779b75933838fefa981f5c0cef0729a223182 Mon Sep 17 00:00:00 2001 From: Dave Griffiths Date: Thu, 6 Oct 2022 20:44:28 +0100 Subject: [PATCH 22/50] separated building and changelog --- building.md | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++ changelog.md | 27 ++++++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 building.md create mode 100644 changelog.md diff --git a/building.md b/building.md new file mode 100644 index 0000000..a6df6ac --- /dev/null +++ b/building.md @@ -0,0 +1,59 @@ +# Building from source +## Linux (Ubuntu) +Install libraries for the sample engine (use brew on mac, MinGW on win): + + $ sudo apt install libsndfile1-dev portaudio19-dev liblo-dev libfftw3-dev + +Install dependencies for the interface: + + $ sudo apt install build-essential qtcreator qt5-default + +Build & run it: + + $ mkdir build + $ cd build + $ qmake .. + $ make + $ sudo make install + $ samplebrain + +## Mac +Install libraries for sample engine: + + $ brew install fftw portaudio liblo libsndfile + +Install dependencies for the interface: + + $ brew install qt + $ brew link qt + +Build & run it: + + $ mkdir build + $ cd build + $ qmake .. + $ make + +`samplebrain.app` should then be in the app folder for you to run. + +# Mac build additions + +To make a mac app bundle: + +Run `macdeployqt` which copies all dependencies inside the app. + + $ cd build + $ macdeployqt + +If the icon is not visible, you might need to copy desktop/samplebrain.icns (the icon) to the Resources directory in the app bundle. + + $ cp ../desktop/samplebrain.icns samplebrain.app/Contents/Resources + +Then edit Info.plist to add samplebrain.icns to CFBundleIconFile. Key `CFBundleIconFile` should match: + + CFBundleIconFile + samplebrain.icns + +You might also need to resign the app bundle after making any changes + + $ codesign --force --deep --sign - samplebrain.app diff --git a/changelog.md b/changelog.md new file mode 100644 index 0000000..f49a480 --- /dev/null +++ b/changelog.md @@ -0,0 +1,27 @@ +# Changlog + +0.18.3 + +* **Windows**: [samplebrain_0.18.3_win.zip](https://static.thentrythis.org/samplebrain/samplebrain_0.18.3_win.zip) +* **Mac (intel/m1)**: [samplebrain_0.18.3_macintel.zip](https://static.thentrythis.org/samplebrain/samplebrain_0.18.3_macintel.app.zip) + +Changes: Release fixes loading samples from paths longer than 255 characters + +0.18.2 + +* **Windows**: [samplebrain_0.18.2_win.zip](https://static.thentrythis.org/samplebrain/samplebrain_0.18.2_win.zip) + +Changes: Crash fix when closing load session file dialog + +0.18.1 + +* **Windows**: [samplebrain_0.18.1_win.zip](https://static.thentrythis.org/samplebrain/samplebrain_0.18.1_win.zip) +* **Mac (intel/m1)**: [samplebrain_0.18.1_macintel.zip](https://static.thentrythis.org/samplebrain/samplebrain_0.18.1_macintel.app.zip) + +Changes: Turned off microphone input to prevent security problems + +0.18 (initial release) + +* **Windows**: [samplebrain_0.18_win.zip](https://static.thentrythis.org/samplebrain/samplebrain_0.18_win.zip) +* **Mac (intel)**: [samplebrain_0.18_macintel.zip](https://static.thentrythis.org/samplebrain/samplebrain_0.18_macintel.zip) +* **Mac (m1)**: [samplebrain_0.18_m1_v2.dmg](https://static.thentrythis.org/samplebrain/samplebrain_0.18_m1_v2.dmg) From a9755b3f724f10f0e029407c2271a85543723c66 Mon Sep 17 00:00:00 2001 From: Dave Griffiths Date: Thu, 6 Oct 2022 20:46:44 +0100 Subject: [PATCH 23/50] readme tweak --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 70f928a..28d3d1a 100644 --- a/README.md +++ b/README.md @@ -58,11 +58,10 @@ binary you need to tell your mac it's ok: Go to System Preferences > Security & Privacy > General. At the bottom of the window, select "Allow apps to be downloaded from Anywhere". -Thank you to [Nik Gaffney](http://fo.am) for help with the Apple builds - -For old versions see the [changelog](changelog.md) - -### Linux +Thank you to [Nik Gaffney](http://fo.am) for help with the Apple +builds. For old versions see the [changelog](changelog.md) + +# Linux install Download on Flathub #### Ubuntu From ed0cf2eaac1284d43a0ad7c97f9ae25fbe72fdf8 Mon Sep 17 00:00:00 2001 From: Dave Griffiths Date: Thu, 6 Oct 2022 20:47:48 +0100 Subject: [PATCH 24/50] readme tweak --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 28d3d1a..7a971d8 100644 --- a/README.md +++ b/README.md @@ -53,13 +53,15 @@ Changes in 0.18.4: New audio device settings window and updated windows build. Better default block size, tool tip tweaks and fixes for dark themes by [Claude Heiland-Allen](https://mathr.co.uk/). +For old versions see the [changelog](changelog.md) + Mac note: As this software is not on the apple store, to run the binary you need to tell your mac it's ok: Go to System Preferences > Security & Privacy > General. At the bottom of the window, select "Allow apps to be downloaded from Anywhere". Thank you to [Nik Gaffney](http://fo.am) for help with the Apple -builds. For old versions see the [changelog](changelog.md) +builds. # Linux install Download on Flathub From 182baa59c3b20ecb042afe8dac2625f7d4ac0a18 Mon Sep 17 00:00:00 2001 From: Dave Griffiths Date: Thu, 6 Oct 2022 20:50:47 +0100 Subject: [PATCH 25/50] readme tweak --- README.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 7a971d8..14a5cdd 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ tweakable parameters until it became slightly out of control. ![](docs/pics/screenshot.png) -Quick start: +# Quick start: 1. Load a bunch of short wav files into the brain 2. Click (re)generate brain @@ -27,18 +27,16 @@ Larger wav files like whole tracks can be used, but take a long time to process, after which they can be saved as "brain" files and instantly reloaded. -# [Demo brain session](https://static.thentrythis.org/samplebrain/demo.samplebrain) +# How do I use this thing? -Load this file using "load session" not "load brain" (sessions contain +Check the [Manual](docs/manual.md) here for the details on all the +parameters and try out the [demo brain session](https://static.thentrythis.org/samplebrain/demo.samplebrain). + +Load the demo using "load session" not "load brain" (sessions contain both the target and brain samples). The original samples used to create the demo session [can be found here for testing](https://static.thentrythis.org/samplebrain/samples/). -# [Manual](docs/manual.md) - -Full description of all the parameters and a bit of the thinking -behind it. - # Download As this is experimental non-commercial software (only originally From 07ea3e63ef392985d542c96335e651d07d6c3561 Mon Sep 17 00:00:00 2001 From: Dave Griffiths Date: Thu, 6 Oct 2022 20:51:42 +0100 Subject: [PATCH 26/50] readme tweak --- README.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 14a5cdd..05e521f 100644 --- a/README.md +++ b/README.md @@ -76,12 +76,9 @@ If you'd like the right font, optionally: # [Building from source](building.md) MFCC algo courtesy of the Aquila library by Zbigniew Siciarz MIT/X11 -licence 2007-2014 (see brain/src/aquila/LICENCE) - -This program is free software licenced under GNU General Public -License version 2 (see LICENCE). - -Written by [Dave Griffiths at Then Try This](http://thentrythis.org). +licence 2007-2014 (see brain/src/aquila/LICENCE). This program is free +software licenced under GNU General Public License version 2 (see +LICENCE). Written by [Dave Griffiths at Then Try This](http://thentrythis.org). ## Links From 7b41c94610e135b8442831caa31e4311e2d6fe36 Mon Sep 17 00:00:00 2001 From: Dave Griffiths Date: Thu, 6 Oct 2022 20:54:40 +0100 Subject: [PATCH 27/50] readme tweak --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 05e521f..cfe71f6 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ tweakable parameters until it became slightly out of control. ![](docs/pics/screenshot.png) -# Quick start: +# How do I use this thing? 1. Load a bunch of short wav files into the brain 2. Click (re)generate brain @@ -27,8 +27,6 @@ Larger wav files like whole tracks can be used, but take a long time to process, after which they can be saved as "brain" files and instantly reloaded. -# How do I use this thing? - Check the [Manual](docs/manual.md) here for the details on all the parameters and try out the [demo brain session](https://static.thentrythis.org/samplebrain/demo.samplebrain). From ac83efce3b53c0ae6ca6723602d168b88b8ecb9e Mon Sep 17 00:00:00 2001 From: Dave Griffiths Date: Thu, 6 Oct 2022 20:55:23 +0100 Subject: [PATCH 28/50] readme tweak --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index cfe71f6..824ef4b 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,6 @@ instantly reloaded. Check the [Manual](docs/manual.md) here for the details on all the parameters and try out the [demo brain session](https://static.thentrythis.org/samplebrain/demo.samplebrain). - Load the demo using "load session" not "load brain" (sessions contain both the target and brain samples). The original samples used to create the demo session [can be found here for From 5469a8f253d7d328173f4d921cb3f223644740dc Mon Sep 17 00:00:00 2001 From: Dave Griffiths Date: Fri, 7 Oct 2022 08:39:45 +0100 Subject: [PATCH 29/50] added build instructions for ubuntu 22.04 fixes: #66 --- building.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/building.md b/building.md index a6df6ac..f6d80c4 100644 --- a/building.md +++ b/building.md @@ -8,6 +8,10 @@ Install dependencies for the interface: $ sudo apt install build-essential qtcreator qt5-default +On ubuntu 22.04 it's: + + $ apt install build-essential qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools + Build & run it: $ mkdir build @@ -57,3 +61,12 @@ Then edit Info.plist to add samplebrain.icns to CFBundleIconFile. Key `CFBundleI You might also need to resign the app bundle after making any changes $ codesign --force --deep --sign - samplebrain.app + +# Windows (Work in progress) + +* Install [MSYS2](https://www.msys2.org/) +* Install dependances via pacman +* Build with qmake as usual +* Run `windeployqt` and copy missing .dll files into release directory + + \ No newline at end of file From 1dc55a982cac05ad35fe158fc15cc71c1f6a78e3 Mon Sep 17 00:00:00 2001 From: fshstk Date: Wed, 12 Oct 2022 00:38:50 +0200 Subject: [PATCH 30/50] Run qmake2cmake command Command used: qmake2cmake_all . --min-qt-version 6.2.4 --- CMakeLists.txt | 105 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..d63dae4 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,105 @@ +cmake_minimum_required(VERSION 3.16) +project(samplebrain VERSION 1.0 LANGUAGES CXX) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +# Set up AUTOMOC and some sensible defaults for runtime execution +# When using Qt 6.3, you can replace the code block below with +# qt_standard_project_setup() +set(CMAKE_AUTOMOC ON) +include(GNUInstallDirs) +set(CMAKE_AUTOUIC ON) + +find_package(QT NAMES Qt5 Qt6 REQUIRED COMPONENTS Core) +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Gui Widgets) + +qt_add_executable(samplebrain WIN32 MACOSX_BUNDLE + app/MainWindow.cpp app/MainWindow.h + app/SettingsDialog.cpp app/SettingsDialog.h + app/audio_thread.cpp + app/feedback.cpp + app/process_thread.cpp + app/qtmain.cpp + app/sound_items.cpp + brain/src/aquila/filter/MelFilter.cpp + brain/src/aquila/filter/MelFilterBank.cpp + brain/src/aquila/transform/Dct.cpp + brain/src/block.cpp + brain/src/block_stream.cpp + brain/src/brain.cpp + brain/src/fft.cpp + brain/src/mfcc.cpp + brain/src/renderer.cpp + brain/src/search_params.cpp + brain/src/spiralcore/OSC_server.cpp + brain/src/spiralcore/allocator.cpp + brain/src/spiralcore/audio.cpp + brain/src/spiralcore/command_ring_buffer.cpp + brain/src/spiralcore/portaudio_client.cpp + brain/src/spiralcore/ring_buffer.cpp + brain/src/spiralcore/sample.cpp + brain/src/spiralcore/stream.cpp + brain/src/status.cpp + brain/src/window.cpp + gui/samplebrain.ui + gui/settings.ui +) +target_include_directories(samplebrain PRIVATE + . + /opt/homebrew/include + /usr/local/include + 2 + brain/src +) + +target_link_libraries(samplebrain PRIVATE + # Remove: L.. + # Remove: L/opt/homebrew/lib + # Remove: L/usr/local/lib + Qt::Core + Qt::Gui + Qt::Widgets + dl + fftw3 + lo + m + portaudio + pthread + sndfile +) + +target_compile_options(samplebrain + -O3 + -Wall + -Wno-unused + -std=c++11 +) + + +# Resources: +set(samplebrain_resource_files + "app/images/at.png" + "app/images/pause.png" + "app/images/play.png" + "app/images/record.png" + "app/images/settings.png" + "app/images/stop.png" +) + +qt_add_resources(samplebrain "samplebrain" + PREFIX + "/images" + BASE + "app" + FILES + ${samplebrain_resource_files} +) + +install(TARGETS samplebrain + BUNDLE DESTINATION . + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) + +# Consider using qt_generate_deploy_app_script() for app deployment if +# the project can use Qt 6.3. In that case rerun qmake2cmake with +# --min-qt-version=6.3. From ddf7f8538adf27e36b5ecb47a31191b36854def6 Mon Sep 17 00:00:00 2001 From: fshstk Date: Fri, 30 Sep 2022 14:38:31 +0200 Subject: [PATCH 31/50] Fix CMakeLists syntax target_compile_options requires the PUBLIC/PRIVATE keyword. I don't know why the qmake2cmake script doesn't provide the correct syntax here... --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d63dae4..91fbe7c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,7 +68,7 @@ target_link_libraries(samplebrain PRIVATE sndfile ) -target_compile_options(samplebrain +target_compile_options(samplebrain PUBLIC -O3 -Wall -Wno-unused From c2e923bceec7ae406beafa9f4ad7bbdc3bd2182a Mon Sep 17 00:00:00 2001 From: fshstk Date: Fri, 30 Sep 2022 14:40:34 +0200 Subject: [PATCH 32/50] Ignore build folder This should cover all files generated by the build system... --- .gitignore | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index b7e2626..378eac2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1 @@ -*.*~ -*.o - +build From 55e5427ec4381bff4b7e28ff38b17cd45dc5108c Mon Sep 17 00:00:00 2001 From: fshstk Date: Mon, 10 Oct 2022 12:39:17 +0200 Subject: [PATCH 33/50] Fix path to .ui files --- app/MainWindow.h | 2 +- app/SettingsDialog.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/MainWindow.h b/app/MainWindow.h index 14d05c4..ab687f5 100644 --- a/app/MainWindow.h +++ b/app/MainWindow.h @@ -18,7 +18,7 @@ #include #include #include -#include "ui_samplebrain.h" +#include "../gui/ui_samplebrain.h" #include "SettingsDialog.h" #include diff --git a/app/SettingsDialog.h b/app/SettingsDialog.h index df5248f..bdc5804 100644 --- a/app/SettingsDialog.h +++ b/app/SettingsDialog.h @@ -20,7 +20,7 @@ #include #include #include -#include "ui_settings.h" +#include "../gui/ui_settings.h" #include #include From 6aa25397deaffd880ea112a83d2e5f998553e898 Mon Sep 17 00:00:00 2001 From: fshstk Date: Fri, 30 Sep 2022 15:57:14 +0200 Subject: [PATCH 34/50] Add fftw3 dependency --- CMakeLists.txt | 2 ++ Dependencies.cmake | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 Dependencies.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 91fbe7c..7f134a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,8 @@ cmake_minimum_required(VERSION 3.16) project(samplebrain VERSION 1.0 LANGUAGES CXX) +include(Dependencies.cmake) + set(CMAKE_INCLUDE_CURRENT_DIR ON) # Set up AUTOMOC and some sensible defaults for runtime execution diff --git a/Dependencies.cmake b/Dependencies.cmake new file mode 100644 index 0000000..0b6a94f --- /dev/null +++ b/Dependencies.cmake @@ -0,0 +1,17 @@ +################################################################################ +# FetchContent +################################################################################ + +include(FetchContent) + +################################################################################ +# fftw3 +################################################################################ + +FetchContent_Declare( + fftw3 + URL http://fftw.org/fftw-3.3.10.tar.gz + URL_HASH MD5=8ccbf6a5ea78a16dbc3e1306e234cc5c) +FetchContent_MakeAvailable(fftw3) + +include_directories(${fftw3_SOURCE_DIR}/api) From 572d6a127c14fcfb1307d8edee867bac1ed6e972 Mon Sep 17 00:00:00 2001 From: fshstk Date: Fri, 30 Sep 2022 15:58:36 +0200 Subject: [PATCH 35/50] Add liblo dependency --- CMakeLists.txt | 2 +- Dependencies.cmake | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f134a7..9224e0b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,7 +63,7 @@ target_link_libraries(samplebrain PRIVATE Qt::Widgets dl fftw3 - lo + lo_shared m portaudio pthread diff --git a/Dependencies.cmake b/Dependencies.cmake index 0b6a94f..909eac1 100644 --- a/Dependencies.cmake +++ b/Dependencies.cmake @@ -15,3 +15,15 @@ FetchContent_Declare( FetchContent_MakeAvailable(fftw3) include_directories(${fftw3_SOURCE_DIR}/api) + +################################################################################ +# liblo +################################################################################ + +FetchContent_Declare( + liblo + URL http://downloads.sourceforge.net/liblo/liblo-0.31.tar.gz + URL_HASH MD5=14378c1e74c58e777fbb4fcf33ac5315) +FetchContent_MakeAvailable(liblo) + +add_subdirectory(${liblo_SOURCE_DIR}/cmake) From 2743caa02a75b2327fb6f1355ae9ddc751ec5efd Mon Sep 17 00:00:00 2001 From: fshstk Date: Fri, 30 Sep 2022 15:58:50 +0200 Subject: [PATCH 36/50] Add portaudio dependency --- CMakeLists.txt | 2 +- Dependencies.cmake | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9224e0b..3b14d96 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.16) -project(samplebrain VERSION 1.0 LANGUAGES CXX) +project(samplebrain VERSION 1.0 LANGUAGES C CXX) include(Dependencies.cmake) diff --git a/Dependencies.cmake b/Dependencies.cmake index 909eac1..1fd857b 100644 --- a/Dependencies.cmake +++ b/Dependencies.cmake @@ -27,3 +27,13 @@ FetchContent_Declare( FetchContent_MakeAvailable(liblo) add_subdirectory(${liblo_SOURCE_DIR}/cmake) + +################################################################################ +# PortAudio +################################################################################ + +FetchContent_Declare( + portaudio + URL http://files.portaudio.com/archives/pa_stable_v190700_20210406.tgz + URL_HASH MD5=ad319249932c6794b551d954b8844402) +FetchContent_MakeAvailable(portaudio) From 30e16d80d91172b9ed690a072632ada3c59c8174 Mon Sep 17 00:00:00 2001 From: fshstk Date: Fri, 30 Sep 2022 16:05:25 +0200 Subject: [PATCH 37/50] Add libsndfile dependency --- Dependencies.cmake | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Dependencies.cmake b/Dependencies.cmake index 1fd857b..848c56a 100644 --- a/Dependencies.cmake +++ b/Dependencies.cmake @@ -37,3 +37,13 @@ FetchContent_Declare( URL http://files.portaudio.com/archives/pa_stable_v190700_20210406.tgz URL_HASH MD5=ad319249932c6794b551d954b8844402) FetchContent_MakeAvailable(portaudio) + +################################################################################ +# libsndfile +################################################################################ + +FetchContent_Declare( + libsndfile + GIT_REPOSITORY https://github.com/libsndfile/libsndfile + GIT_TAG 1.1.0) +FetchContent_MakeAvailable(libsndfile) From 2c1a3692e7214f564dc93ab7c612736315cfe0ca Mon Sep 17 00:00:00 2001 From: fshstk Date: Mon, 10 Oct 2022 12:42:18 +0200 Subject: [PATCH 38/50] Update building.md - Add info about installing cmake - Change build instructions from qmake to cmake - Remove info about installing deps We don't need to do this anymore. CMake will do it automatically at configure-time! --- building.md | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/building.md b/building.md index f6d80c4..90c068e 100644 --- a/building.md +++ b/building.md @@ -1,8 +1,8 @@ # Building from source ## Linux (Ubuntu) -Install libraries for the sample engine (use brew on mac, MinGW on win): +Install cmake: - $ sudo apt install libsndfile1-dev portaudio19-dev liblo-dev libfftw3-dev + $ sudo apt install cmake Install dependencies for the interface: @@ -16,15 +16,14 @@ Build & run it: $ mkdir build $ cd build - $ qmake .. - $ make - $ sudo make install - $ samplebrain + $ cmake .. + $ cmake --build . + $ ./samplebrain ## Mac -Install libraries for sample engine: +Install cmake: - $ brew install fftw portaudio liblo libsndfile + $ brew install cmake Install dependencies for the interface: @@ -35,10 +34,10 @@ Build & run it: $ mkdir build $ cd build - $ qmake .. - $ make + $ cmake .. + $ cmake --build . -`samplebrain.app` should then be in the app folder for you to run. +`samplebrain.app` should then be in the build folder for you to run. # Mac build additions From 7d7a7d24a22b10a49f89a34077f9a326f3aa4fdd Mon Sep 17 00:00:00 2001 From: fshstk Date: Mon, 10 Oct 2022 12:45:52 +0200 Subject: [PATCH 39/50] Remove Qt .pro file CMakeLists.txt now does everything we need --- samplebrain.pro | 71 ------------------------------------------------- 1 file changed, 71 deletions(-) delete mode 100644 samplebrain.pro diff --git a/samplebrain.pro b/samplebrain.pro deleted file mode 100644 index 8b047df..0000000 --- a/samplebrain.pro +++ /dev/null @@ -1,71 +0,0 @@ -###################################################################### -# Automatically generated by qmake (2.01a) Sun Jul 5 17:49:45 2015 -###################################################################### - -TEMPLATE = app -TARGET = samplebrain -DEPENDPATH += . 2 -INCLUDEPATH += . 2 - -QT += core gui widgets - -# Input -HEADERS += app/MainWindow.h \ - app/SettingsDialog.h - -FORMS += gui/samplebrain.ui \ - gui/settings.ui - -SOURCES += app/MainWindow.cpp \ - app/SettingsDialog.cpp \ - app/sound_items.cpp \ - app/audio_thread.cpp \ - app/process_thread.cpp \ - app/feedback.cpp \ - app/qtmain.cpp \ - brain/src/block.cpp \ - brain/src/brain.cpp \ - brain/src/fft.cpp \ - brain/src/mfcc.cpp \ - brain/src/renderer.cpp \ - brain/src/search_params.cpp \ - brain/src/status.cpp \ - brain/src/window.cpp \ - brain/src/block_stream.cpp \ - brain/src/aquila/filter/MelFilterBank.cpp \ - brain/src/aquila/filter/MelFilter.cpp \ - brain/src/aquila/transform/Dct.cpp \ - brain/src/spiralcore/sample.cpp \ - brain/src/spiralcore/ring_buffer.cpp \ - brain/src/spiralcore/command_ring_buffer.cpp \ - brain/src/spiralcore/portaudio_client.cpp \ - brain/src/spiralcore/audio.cpp \ - brain/src/spiralcore/OSC_server.cpp \ - brain/src/spiralcore/allocator.cpp \ - brain/src/spiralcore/stream.cpp - -INCLUDEPATH += brain/src -INCLUDEPATH += /usr/local/include -INCLUDEPATH += /opt/homebrew/include -LIBS += -L.. -L/usr/local/lib -L/opt/homebrew/lib -lportaudio -lfftw3 -lsndfile -llo -ldl -lpthread -lm - -QMAKE_CXXFLAGS += -O3 -Wall -Wno-unused -std=c++11 - -# assets -RESOURCES = app/samplebrain.qrc -ICON = desktop/samplebrain.icns - -PREFIX = $$(PREFIX) -isEmpty(PREFIX) { - PREFIX = /usr -} - -unix:desktopfile.path = $$PREFIX/share/applications/ -unix:desktopfile.files = desktop/samplebrain.desktop -unix:iconfile.path = $$PREFIX/share/icons/hicolor/scalable/apps -unix:iconfile.files = desktop/samplebrain.svg -unix:metainfofile.path = $$PREFIX/share/metainfo -unix:metainfofile.files = desktop/org.thentrythis.Samplebrain.metainfo.xml - -target.path = $$PREFIX/bin -INSTALLS += target desktopfile iconfile metainfofile From 6471b1e06625c6e298ad6cbf0ab3a6001eb7db42 Mon Sep 17 00:00:00 2001 From: fshstk Date: Wed, 12 Oct 2022 00:43:35 +0200 Subject: [PATCH 40/50] Clean up CMakeLists - Remove redundant libraries - Change compile definitions to CMake variables --- CMakeLists.txt | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b14d96..4bdb307 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,9 @@ set(CMAKE_AUTOMOC ON) include(GNUInstallDirs) set(CMAKE_AUTOUIC ON) +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + find_package(QT NAMES Qt5 Qt6 REQUIRED COMPONENTS Core) find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Gui Widgets) @@ -48,36 +51,19 @@ qt_add_executable(samplebrain WIN32 MACOSX_BUNDLE ) target_include_directories(samplebrain PRIVATE . - /opt/homebrew/include - /usr/local/include - 2 brain/src ) target_link_libraries(samplebrain PRIVATE - # Remove: L.. - # Remove: L/opt/homebrew/lib - # Remove: L/usr/local/lib Qt::Core Qt::Gui Qt::Widgets - dl fftw3 lo_shared - m portaudio - pthread sndfile ) -target_compile_options(samplebrain PUBLIC - -O3 - -Wall - -Wno-unused - -std=c++11 -) - - # Resources: set(samplebrain_resource_files "app/images/at.png" From cf06a2116036400c3e0253482c32db6b3d3939b1 Mon Sep 17 00:00:00 2001 From: fshstk Date: Wed, 12 Oct 2022 15:21:12 +0200 Subject: [PATCH 41/50] Add Gitlab CI script --- .gitlab-ci.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..cf81e47 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,10 @@ +build-job: + stage: build + image: ubuntu:22.04 + script: + - apt-get update + - apt-get install -y git cmake g++ freeglut3-dev qt6-base-dev + - mkdir -p build + - cd build + - cmake .. + - cmake --build . From 5e347f7f134254381b2acadf9ceb56212de3a30d Mon Sep 17 00:00:00 2001 From: Dave Griffiths Date: Fri, 14 Oct 2022 18:27:59 +0100 Subject: [PATCH 42/50] made the 3 osc ports configurable via (cross-platform) config scripts and move them outside of well used ranges for UDP --- app/MainWindow.cpp | 10 +- app/MainWindow.h | 593 ++++++++++++++-------------- app/audio_thread.cpp | 11 +- app/audio_thread.h | 4 +- app/feedback.h | 3 +- app/process_thread.cpp | 4 +- app/process_thread.h | 13 +- app/qtmain.cpp | 48 ++- brain/src/spiralcore/OSC_server.cpp | 188 +++++---- brain/src/spiralcore/OSC_server.h | 25 +- brain/src/status.cpp | 4 + brain/src/status.h | 1 + 12 files changed, 472 insertions(+), 432 deletions(-) diff --git a/app/MainWindow.cpp b/app/MainWindow.cpp index 8a0555f..2b0da8e 100644 --- a/app/MainWindow.cpp +++ b/app/MainWindow.cpp @@ -27,9 +27,11 @@ using namespace std; -MainWindow::MainWindow() : +MainWindow::MainWindow(const string &port, const string &audio_port, const string &process_port) : m_last_file("."), - m_feedback("8890") + m_feedback(port), + m_audio_port(audio_port), + m_process_port(process_port) { m_sound_item_enable_mapper = new QSignalMapper(this); m_sound_item_delete_mapper = new QSignalMapper(this); @@ -59,8 +61,8 @@ MainWindow::MainWindow() : for (int i=0; i<10; i++) { osc_destination d; d.m_id=i; - d.m_audio_address = lo_address_new_from_url("osc.udp://localhost:8888"); - d.m_process_address = lo_address_new_from_url("osc.udp://localhost:8889"); + d.m_audio_address = lo_address_new_from_url(string("osc.udp://localhost:"+m_audio_port).c_str()); + d.m_process_address = lo_address_new_from_url(string("osc.udp://localhost:"+m_process_port).c_str()); if (i==0) d.m_enabled=true; else d.m_enabled=false; add_gui_address(d,enable_mapper); diff --git a/app/MainWindow.h b/app/MainWindow.h index 14d05c4..f68cc3c 100644 --- a/app/MainWindow.h +++ b/app/MainWindow.h @@ -18,6 +18,7 @@ #include #include #include +#include #include "ui_samplebrain.h" #include "SettingsDialog.h" @@ -34,11 +35,19 @@ using namespace spiralcore; class MainWindow : public QMainWindow { - Q_OBJECT + Q_OBJECT public: - MainWindow(); + MainWindow(const string &port, const string &audio_port, const string &process_port); + void message(const string &msg) { + QMessageBox::information(this,"problem",msg.c_str(), QMessageBox::Ok); + } + + bool ok() { + return m_feedback.ok(); + } + // all this to work around liblo's use of varargs... void send_audio_osc(const char *name, const char *types) { for (auto dest:m_destinations) { @@ -102,344 +111,346 @@ protected: private slots: - void play_slot() { send_audio_osc("/start",""); } - void stop_slot() { send_audio_osc("/pause",""); } + void play_slot() { send_audio_osc("/start",""); } + void stop_slot() { send_audio_osc("/pause",""); } - void ratio_slot(int s) { - send_audio_osc("/ratio","f",s/100.0f); - m_Ui.doubleSpinBoxRatio->setValue(s/100.0f); - } - void ratio_slot(double s) { - send_audio_osc("/ratio","f",s); - m_Ui.sliderRatio->setValue(s*100); - } + void ratio_slot(int s) { + send_audio_osc("/ratio","f",s/100.0f); + m_Ui.doubleSpinBoxRatio->setValue(s/100.0f); + } + void ratio_slot(double s) { + send_audio_osc("/ratio","f",s); + m_Ui.sliderRatio->setValue(s*100); + } - void n_ratio_slot(int s) { - send_audio_osc("/n_ratio","f",s/100.0f); - m_Ui.doubleSpinBoxNRatio->setValue(s/100.0f); - } - void n_ratio_slot(double s) { - send_audio_osc("/n_ratio","f",s); - m_Ui.sliderNRatio->setValue(s*100); - } + void n_ratio_slot(int s) { + send_audio_osc("/n_ratio","f",s/100.0f); + m_Ui.doubleSpinBoxNRatio->setValue(s/100.0f); + } + void n_ratio_slot(double s) { + send_audio_osc("/n_ratio","f",s); + m_Ui.sliderNRatio->setValue(s*100); + } - void autotune(int s) { - send_audio_osc("/autotune","f",s/100.0f); - m_Ui.doubleSpinBoxAutotune->setValue(s/100.0f); - } - void autotune(double s) { - send_audio_osc("/autotune","f",s); - m_Ui.sliderAutotune->setValue(s*100); - } + void autotune(int s) { + send_audio_osc("/autotune","f",s/100.0f); + m_Ui.doubleSpinBoxAutotune->setValue(s/100.0f); + } + void autotune(double s) { + send_audio_osc("/autotune","f",s); + m_Ui.sliderAutotune->setValue(s*100); + } - void fft1_start_slot(int s) { send_audio_osc("/fft1_start","i",s); } - void fft1_end_slot(int s) { send_audio_osc("/fft1_end","i",s); } - void fft2_start_slot(int s){} // { m_renderer->get_params()->m_fft2_start=s; } - void fft2_end_slot(int s){} // { m_renderer->get_params()->m_fft2_end=s; } + void fft1_start_slot(int s) { send_audio_osc("/fft1_start","i",s); } + void fft1_end_slot(int s) { send_audio_osc("/fft1_end","i",s); } + void fft2_start_slot(int s){} // { m_renderer->get_params()->m_fft2_start=s; } + void fft2_end_slot(int s){} // { m_renderer->get_params()->m_fft2_end=s; } - void n_mix_slot(int s) { - send_audio_osc("/n_mix","f",s/100.0f); - m_Ui.doubleSpinBoxNMix->setValue(s/100.0f); - } - void n_mix_slot(double s) { - send_audio_osc("/n_mix","f",s); - m_Ui.sliderNMix->setValue(s*100); - } + void n_mix_slot(int s) { + send_audio_osc("/n_mix","f",s/100.0f); + m_Ui.doubleSpinBoxNMix->setValue(s/100.0f); + } + void n_mix_slot(double s) { + send_audio_osc("/n_mix","f",s); + m_Ui.sliderNMix->setValue(s*100); + } - void novelty_slot(int s) { - send_audio_osc("/novelty","f",s/100.0f); - m_Ui.doubleSpinBoxNovelty->setValue(s/100.0f); - } - void novelty_slot(double s) { - send_audio_osc("/novelty","f",s); - m_Ui.sliderNovelty->setValue(s*100); - } + void novelty_slot(int s) { + send_audio_osc("/novelty","f",s/100.0f); + m_Ui.doubleSpinBoxNovelty->setValue(s/100.0f); + } + void novelty_slot(double s) { + send_audio_osc("/novelty","f",s); + m_Ui.sliderNovelty->setValue(s*100); + } - void boredom_slot(int s) { - float v=s/100.0f; - send_audio_osc("/boredom","f",v); - m_Ui.doubleSpinBoxBoredom->setValue(v); - } - void boredom_slot(double s) { - send_audio_osc("/boredom","f",s); - m_Ui.sliderBoredom->setValue(s*100); - } - void synapses(int s) { - send_audio_osc("/synapses","i",s); - } - void target_mix_slot(int s) { - send_audio_osc("/target_mix","f",s/100.0f); - m_Ui.doubleSpinBoxTargetMix->setValue(s/100.0f); - } - void target_mix_slot(double s) { - send_audio_osc("/target_mix","f",s); - m_Ui.sliderTargetMix->setValue(s*100); - } - void search_stretch(int s) { - send_audio_osc("/search-stretch","i",s); - } - void slide_error(int s) { - send_audio_osc("/slide-error","i",s); - } - void stickyness_slot(int s) { - send_audio_osc("/stickyness","f",s/100.0f); - m_Ui.doubleSpinBoxStickyness->setValue(s/100.0f); - } - void stickyness_slot(double s) { - send_audio_osc("/stickyness","f",s); - m_Ui.sliderStickyness->setValue(s*100); - } + void boredom_slot(int s) { + float v=s/100.0f; + send_audio_osc("/boredom","f",v); + m_Ui.doubleSpinBoxBoredom->setValue(v); + } + void boredom_slot(double s) { + send_audio_osc("/boredom","f",s); + m_Ui.sliderBoredom->setValue(s*100); + } + void synapses(int s) { + send_audio_osc("/synapses","i",s); + } + void target_mix_slot(int s) { + send_audio_osc("/target_mix","f",s/100.0f); + m_Ui.doubleSpinBoxTargetMix->setValue(s/100.0f); + } + void target_mix_slot(double s) { + send_audio_osc("/target_mix","f",s); + m_Ui.sliderTargetMix->setValue(s*100); + } + void search_stretch(int s) { + send_audio_osc("/search-stretch","i",s); + } + void slide_error(int s) { + send_audio_osc("/slide-error","i",s); + } + void stickyness_slot(int s) { + send_audio_osc("/stickyness","f",s/100.0f); + m_Ui.doubleSpinBoxStickyness->setValue(s/100.0f); + } + void stickyness_slot(double s) { + send_audio_osc("/stickyness","f",s); + m_Ui.sliderStickyness->setValue(s*100); + } - void volume_slot(int s) { send_audio_osc("/volume","f",s/100.0f); } - void algo(int n) { send_audio_osc("/search_algo","i",n); } + void volume_slot(int s) { send_audio_osc("/volume","f",s/100.0f); } + void algo(int n) { send_audio_osc("/search_algo","i",n); } - void run_slot() {} - void load_target() { - m_last_file=QFileDialog::getOpenFileName( - this, - QString("Select an wav file"), - m_last_file, - QString("Sounds (*.wav)")); + void run_slot() {} + void load_target() { + m_last_file=QFileDialog::getOpenFileName( + this, + QString("Select an wav file"), + m_last_file, + QString("Sounds (*.wav)")); - send_process_osc("/load_target","s",m_last_file.toStdString().c_str()); - } - void target_block_size(int s) { send_process_osc("/target_block_size","i",s); } - void target_block_overlap(double s) { send_process_osc("/target_overlap","f",s); } - void generate_target_blocks() { send_process_osc("/generate_target",""); } - void block_size(int s) { send_process_osc("/source_block_size","i",s); } - void block_overlap(double s) { send_process_osc("/source_overlap","f",s); } - void fft_spectrum_size(int) {} - void generate() { send_process_osc("/generate_brain",""); } - void load_sound() { - m_last_file=QFileDialog::getOpenFileName( - this, - QString("Select a wav file"), - m_last_file, - QString("Sounds (*.wav)")); + send_process_osc("/load_target","s",m_last_file.toStdString().c_str()); + } + void target_block_size(int s) { send_process_osc("/target_block_size","i",s); } + void target_block_overlap(double s) { send_process_osc("/target_overlap","f",s); } + void generate_target_blocks() { send_process_osc("/generate_target",""); } + void block_size(int s) { send_process_osc("/source_block_size","i",s); } + void block_overlap(double s) { send_process_osc("/source_overlap","f",s); } + void fft_spectrum_size(int) {} + void generate() { send_process_osc("/generate_brain",""); } + void load_sound() { + m_last_file=QFileDialog::getOpenFileName( + this, + QString("Select a wav file"), + m_last_file, + QString("Sounds (*.wav)")); - send_process_osc("/load_sample","s",m_last_file.toStdString().c_str()); + send_process_osc("/load_sample","s",m_last_file.toStdString().c_str()); - sound_items::sound_item &si = m_sound_items.add(m_Ui.brain_contents, m_last_file.toStdString(),true); + sound_items::sound_item &si = m_sound_items.add(m_Ui.brain_contents, m_last_file.toStdString(),true); + + QObject::connect(si.m_enable, SIGNAL(clicked()), m_sound_item_enable_mapper, SLOT(map())); + m_sound_item_enable_mapper->setMapping(si.m_enable, si.m_id); + QObject::connect(si.m_del, SIGNAL(clicked()), m_sound_item_delete_mapper, SLOT(map())); + m_sound_item_delete_mapper->setMapping(si.m_del, si.m_id); + } + + + void load_sounds() { + m_last_file=QFileDialog::getExistingDirectory(this, + QString("Select a directory"), + m_last_file); + + + QDirIterator dirIt(m_last_file,QDirIterator::Subdirectories); + while (dirIt.hasNext()) { + dirIt.next(); + if (QFileInfo(dirIt.filePath()).isFile() && + QFileInfo(dirIt.filePath()).suffix() == "wav") { + send_process_osc("/load_sample","s",dirIt.filePath().toStdString().c_str()); + + sound_items::sound_item &si = m_sound_items.add(m_Ui.brain_contents, dirIt.filePath().toStdString(),true); QObject::connect(si.m_enable, SIGNAL(clicked()), m_sound_item_enable_mapper, SLOT(map())); m_sound_item_enable_mapper->setMapping(si.m_enable, si.m_id); QObject::connect(si.m_del, SIGNAL(clicked()), m_sound_item_delete_mapper, SLOT(map())); m_sound_item_delete_mapper->setMapping(si.m_del, si.m_id); + + } } + } + void select_all() { + for (auto &si:m_sound_items.m_sound_items) { + si.m_enable->setChecked(true); + send_process_osc("/activate_sound","s",si.m_filename.c_str()); + } + } - void load_sounds() { - m_last_file=QFileDialog::getExistingDirectory(this, - QString("Select a directory"), - m_last_file); + void select_none() { + for (auto &si:m_sound_items.m_sound_items) { + si.m_enable->setChecked(false); + send_process_osc("/deactivate_sound","s",si.m_filename.c_str()); + } + } - - QDirIterator dirIt(m_last_file,QDirIterator::Subdirectories); - while (dirIt.hasNext()) { - dirIt.next(); - if (QFileInfo(dirIt.filePath()).isFile() && - QFileInfo(dirIt.filePath()).suffix() == "wav") { - send_process_osc("/load_sample","s",dirIt.filePath().toStdString().c_str()); - - sound_items::sound_item &si = m_sound_items.add(m_Ui.brain_contents, dirIt.filePath().toStdString(),true); - - QObject::connect(si.m_enable, SIGNAL(clicked()), m_sound_item_enable_mapper, SLOT(map())); - m_sound_item_enable_mapper->setMapping(si.m_enable, si.m_id); - QObject::connect(si.m_del, SIGNAL(clicked()), m_sound_item_delete_mapper, SLOT(map())); - m_sound_item_delete_mapper->setMapping(si.m_del, si.m_id); - - } - } - } - - void select_all() { - for (auto &si:m_sound_items.m_sound_items) { - si.m_enable->setChecked(true); - send_process_osc("/activate_sound","s",si.m_filename.c_str()); - } - } - - void select_none() { - for (auto &si:m_sound_items.m_sound_items) { - si.m_enable->setChecked(false); - send_process_osc("/deactivate_sound","s",si.m_filename.c_str()); - } - } - - void sound_enable(int id) { - // search for this id... - for (auto &si:m_sound_items.m_sound_items) { - if (si.m_id==id) { - if (si.m_enable->isChecked()) { - send_process_osc("/activate_sound","s",si.m_filename.c_str()); - } else { - send_process_osc("/deactivate_sound","s",si.m_filename.c_str()); - } + void sound_enable(int id) { + // search for this id... + for (auto &si:m_sound_items.m_sound_items) { + if (si.m_id==id) { + if (si.m_enable->isChecked()) { + send_process_osc("/activate_sound","s",si.m_filename.c_str()); + } else { + send_process_osc("/deactivate_sound","s",si.m_filename.c_str()); } } } + } - void delete_sound(int id) { - // search for this id... - for (auto &si:m_sound_items.m_sound_items) { - if (si.m_id==id) { - send_process_osc("/delete_sample","s",si.m_filename.c_str()); - m_sound_items.remove(si.m_filename); - // iterator is now invalidated... - return; - } - } - } - void clear_brain() { - for (auto &si:m_sound_items.m_sound_items) { + void delete_sound(int id) { + // search for this id... + for (auto &si:m_sound_items.m_sound_items) { + if (si.m_id==id) { send_process_osc("/delete_sample","s",si.m_filename.c_str()); + m_sound_items.remove(si.m_filename); + // iterator is now invalidated... + return; } - m_sound_items.clear(); } - void restart_audio() { send_audio_osc("/restart_audio",""); } - - void brain_shape(int n) { send_process_osc("/window_type","i",n); } - void target_shape(int n) { send_process_osc("/target_window_type","i",n); } - void mic(bool n) { send_audio_osc("/mic","i",(int)n); } - - void record() { - if (m_save_wav=="") { - m_last_file=QFileDialog::getSaveFileName( - this, - QString("Select a wav file"), - m_last_file, - QString("Sounds (*.wav)")); - m_save_wav = m_last_file.toStdString(); - // chop off .wav - size_t pos = m_save_wav.find_last_of("."); - if (pos!=string::npos) { - m_save_wav = m_save_wav.substr(0,pos); - } - - } - - char fn[1024]; - snprintf(fn,1024,"%s-%i",m_save_wav.c_str(),m_record_id); - send_audio_osc("/record","s",fn); - cerr<isChecked()) { - // reconnect - string url = d.m_address->text().toUtf8().constData(); - lo_address_free(d.m_audio_address); - lo_address_free(d.m_process_address); - d.m_audio_address = lo_address_new_from_url(string(url+":8888").c_str()); - d.m_process_address = lo_address_new_from_url(string(url+":8889").c_str()); - // start sending messages here - d.m_enabled=true; - } else { - // stop sending messages here - d.m_enabled=false; + void record() { + if (m_save_wav=="") { + m_last_file=QFileDialog::getSaveFileName( + this, + QString("Select a wav file"), + m_last_file, + QString("Sounds (*.wav)")); + m_save_wav = m_last_file.toStdString(); + // chop off .wav + size_t pos = m_save_wav.find_last_of("."); + if (pos!=string::npos) { + m_save_wav = m_save_wav.substr(0,pos); } } + char fn[1024]; + snprintf(fn,1024,"%s-%i",m_save_wav.c_str(),m_record_id); + send_audio_osc("/record","s",fn); + cerr<isChecked()) { + // reconnect + string url = d.m_address->text().toUtf8().constData(); + lo_address_free(d.m_audio_address); + lo_address_free(d.m_process_address); + d.m_audio_address = lo_address_new_from_url(string(url+":"+m_audio_port).c_str()); + d.m_process_address = lo_address_new_from_url(string(url+":"+m_process_port).c_str()); + // start sending messages here + d.m_enabled=true; + } else { + // stop sending messages here + d.m_enabled=false; + } + + } + void settings() { m_settings_dialog->show(); } private: - /////////////////////////////////////////////// + /////////////////////////////////////////////// - // we want to be able to send out to - // multiple addresses over the network + // we want to be able to send out to + // multiple addresses over the network - //////////////////////////////////////////////// + //////////////////////////////////////////////// - // we want to be able to send out to - // multiple addresses over the network - class osc_destination { - public: - int m_id; - lo_address m_audio_address; - lo_address m_process_address; - // can't find a way to address these via qt - QLineEdit *m_address; - QCheckBox *m_enable; - bool m_enabled; - }; + // we want to be able to send out to + // multiple addresses over the network + class osc_destination { + public: + int m_id; + lo_address m_audio_address; + lo_address m_process_address; + // can't find a way to address these via qt + QLineEdit *m_address; + QCheckBox *m_enable; + bool m_enabled; + }; - vector m_destinations; + vector m_destinations; - void init_from_session(const string &filename); - void add_gui_address(osc_destination &dest, - QSignalMapper* enable_mapper); + void init_from_session(const string &filename); + void add_gui_address(osc_destination &dest, + QSignalMapper* enable_mapper); - string m_save_wav; - QString m_last_file; - unsigned int m_record_id; - Ui_MainWindow m_Ui; - feedback m_feedback; - QSignalMapper* m_sound_item_enable_mapper; - QSignalMapper* m_sound_item_delete_mapper; - sound_items m_sound_items; + string m_save_wav; + QString m_last_file; + unsigned int m_record_id; + Ui_MainWindow m_Ui; + feedback m_feedback; + QSignalMapper* m_sound_item_enable_mapper; + QSignalMapper* m_sound_item_delete_mapper; + sound_items m_sound_items; - SettingsDialog *m_settings_dialog; + SettingsDialog *m_settings_dialog; audio_thread *m_audio_thread; - + + string m_audio_port; + string m_process_port; }; diff --git a/app/audio_thread.cpp b/app/audio_thread.cpp index 1d51bcf..7da0aa0 100644 --- a/app/audio_thread.cpp +++ b/app/audio_thread.cpp @@ -20,26 +20,23 @@ using namespace spiralcore; using namespace std; -audio_thread::audio_thread(process_thread &p) : +audio_thread::audio_thread(const string &port, process_thread &p) : m_audio_device(NULL), - m_osc("8888"), + m_osc(port), m_process_thread(p), m_brain_mutex(p.m_brain_mutex), m_stereo_mode(false), m_mic_mode(false), m_bufsize(2048), m_samplerate(44100), - m_device("") -{ - // start_audio(); + m_device("") { + pthread_mutex_lock(m_brain_mutex); m_left_renderer = new renderer(p.m_source,p.m_left_target); m_right_renderer = new renderer(p.m_source,p.m_right_target); m_block_stream = new block_stream(); pthread_mutex_unlock(m_brain_mutex); m_osc.run(); - // it this threadsafe? - // m_audio_device->report_devices(); } static bool state = 1; diff --git a/app/audio_thread.h b/app/audio_thread.h index bcee447..66ccd40 100644 --- a/app/audio_thread.h +++ b/app/audio_thread.h @@ -26,12 +26,12 @@ namespace spiralcore { class audio_thread { public: - audio_thread(process_thread &p); + audio_thread(const string &port, process_thread &p); ~audio_thread(); void start_audio(); void restart_audio(const string device, unsigned int samplerate, unsigned int bufsize); - + bool ok() { return m_osc.ok(); } void process(sample &left_in, sample &right_in, sample &left_out, sample &right_out); static void run_audio(void* c, unsigned int frames); diff --git a/app/feedback.h b/app/feedback.h index 4de48d8..1c52949 100644 --- a/app/feedback.h +++ b/app/feedback.h @@ -29,7 +29,8 @@ class feedback { public: feedback(std::string address); void poll(QStatusBar *s, sound_items *sound_items, SettingsDialog *settings); - + bool ok() { return m_osc.ok(); } + private: OSC_server m_osc; diff --git a/app/process_thread.cpp b/app/process_thread.cpp index 8208b5b..d09e127 100644 --- a/app/process_thread.cpp +++ b/app/process_thread.cpp @@ -32,8 +32,8 @@ static void* _process(void *c) { return NULL; } -process_thread::process_thread() : - m_osc("8889"), +process_thread::process_thread(const string &port) : + m_osc(port), m_source_block_size(1000), m_source_overlap(0.75), m_target_block_size(1000), diff --git a/app/process_thread.h b/app/process_thread.h index 7464f45..8523d69 100644 --- a/app/process_thread.h +++ b/app/process_thread.h @@ -25,9 +25,9 @@ namespace spiralcore { -class process_thread { -public: - process_thread(); + class process_thread { + public: + process_thread(const string &port); ~process_thread(); pthread_mutex_t* m_brain_mutex; @@ -47,10 +47,13 @@ public: void load_session(const std::string &filename); void save_session(const std::string &filename); + + bool ok() { return m_osc.ok(); } + // only for use in mutex brain m_source, m_left_target, m_right_target; -private: + private: OSC_server m_osc; u32 m_source_block_size; float m_source_overlap; @@ -64,6 +67,6 @@ private: renderer *m_left_renderer; renderer *m_right_renderer; block_stream *m_block_stream; -}; + }; } diff --git a/app/qtmain.cpp b/app/qtmain.cpp index c505db3..f2c5cdb 100644 --- a/app/qtmain.cpp +++ b/app/qtmain.cpp @@ -19,22 +19,54 @@ #include #include #include +#include + #include "MainWindow.h" #include "process_thread.h" #include "audio_thread.h" +#include "status.h" using namespace std; int main( int argc , char *argv[] ){ - QApplication app(argc, argv); - MainWindow mainWin; - mainWin.show(); - process_thread pt; - audio_thread at(pt); - pt.register_renderer(at.m_left_renderer, at.m_right_renderer, at.m_block_stream); - mainWin.set_audio_thread(&at); + + QApplication app(argc, argv); + + QSettings settings("thentrythis", "samplebrain"); - return app.exec(); + // slight over-use of OSC servers here, but the are packaged nicely for + // threadsafe (nonblocking) communication, and it's useful to expose all + // moving parts for external control (i.e. the processing and the audio) + + if (!settings.contains("gui_port")) settings.setValue("gui_port", "62345"); + if (!settings.contains("audio_port")) settings.setValue("audio_port", "62346"); + if (!settings.contains("process_port")) settings.setValue("process_port", "62347"); + + string gui_port = settings.value("gui_port").toByteArray().constData(); + string audio_port = settings.value("audio_port").toByteArray().constData(); + string process_port = settings.value("process_port").toByteArray().constData(); + + status::set_port(gui_port); + + MainWindow mainWin(gui_port,audio_port,process_port); + mainWin.show(); + process_thread pt(process_port); + audio_thread at(audio_port,pt); + + pt.register_renderer(at.m_left_renderer, at.m_right_renderer, at.m_block_stream); + mainWin.set_audio_thread(&at); + + if (!at.ok()) { + mainWin.message("problem starting audio thread on port "+audio_port); + } + if (!pt.ok()) { + mainWin.message("problem starting process thread on port "+process_port); + } + if (!mainWin.ok()) { + mainWin.message("problem starting gui thread on port "+gui_port); + } + + return app.exec(); } diff --git a/brain/src/spiralcore/OSC_server.cpp b/brain/src/spiralcore/OSC_server.cpp index cfd1590..e37fa4a 100644 --- a/brain/src/spiralcore/OSC_server.cpp +++ b/brain/src/spiralcore/OSC_server.cpp @@ -31,117 +31,105 @@ extern "C" { #endif OSC_server::OSC_server(const string &port) : -m_port(port), -m_exit(false), -m_command_ring_buffer(262144) -{ - //cerr<<"using port: ["<COMMAND_DATA_SIZE) - { - cerr<<"osc data too big for ringbuffer command"<COMMAND_DATA_SIZE) - { - cerr<<"osc data too big for ringbuffer command"<s); - - if (pos+size+1>COMMAND_DATA_SIZE) - { - cerr<<"osc data too big for ringbuffer command"<s,size); - newdata[pos+size]='\0'; - pos+=size+1; - } - break; - default: - { - cerr<<"unsupported type: "<m_command_ring_buffer.send(command)) - { - //cerr<<"OSC_server - ringbuffer full!"<COMMAND_DATA_SIZE) { + cerr<<"osc data too big for ringbuffer command"<COMMAND_DATA_SIZE) { + cerr<<"osc data too big for ringbuffer command"<s); + + if (pos+size+1>COMMAND_DATA_SIZE) { + cerr<<"osc data too big for ringbuffer command"<s,size); + newdata[pos+size]='\0'; + pos+=size+1; + } + break; + default: { + cerr<<"unsupported type: "<m_command_ring_buffer.send(command)) { + //cerr<<"OSC_server - ringbuffer full!"< Date: Fri, 14 Oct 2022 19:23:07 +0100 Subject: [PATCH 43/50] added osc ports to the settings interface, bumped the version num --- app/MainWindow.cpp | 4 +- app/MainWindow.h | 7 +- app/SettingsDialog.cpp | 8 +- app/SettingsDialog.h | 26 +++- app/qtmain.cpp | 2 +- brain/src/spiralcore/OSC_server.cpp | 7 +- gui/samplebrain.ui | 2 +- gui/settings.ui | 197 ++++++++++++++++++++++++++-- 8 files changed, 224 insertions(+), 29 deletions(-) diff --git a/app/MainWindow.cpp b/app/MainWindow.cpp index 2b0da8e..5e0eb52 100644 --- a/app/MainWindow.cpp +++ b/app/MainWindow.cpp @@ -27,7 +27,7 @@ using namespace std; -MainWindow::MainWindow(const string &port, const string &audio_port, const string &process_port) : +MainWindow::MainWindow(const string &port, const string &audio_port, const string &process_port, QSettings *settings) : m_last_file("."), m_feedback(port), m_audio_port(audio_port), @@ -51,7 +51,7 @@ MainWindow::MainWindow(const string &port, const string &audio_port, const strin m_Ui.brain_contents->setSpacing(0); m_Ui.brain_contents->setContentsMargins(0,0,0,0); - m_settings_dialog = new SettingsDialog(this); + m_settings_dialog = new SettingsDialog(this,settings); // add default local dest // turn on first one diff --git a/app/MainWindow.h b/app/MainWindow.h index f68cc3c..7500cdb 100644 --- a/app/MainWindow.h +++ b/app/MainWindow.h @@ -33,13 +33,15 @@ using namespace std; using namespace spiralcore; +class QSettings; + class MainWindow : public QMainWindow { Q_OBJECT public: - MainWindow(const string &port, const string &audio_port, const string &process_port); - + MainWindow(const string &port, const string &audio_port, const string &process_port, QSettings *settings); + void message(const string &msg) { QMessageBox::information(this,"problem",msg.c_str(), QMessageBox::Ok); } @@ -453,4 +455,5 @@ private: string m_audio_port; string m_process_port; + }; diff --git a/app/SettingsDialog.cpp b/app/SettingsDialog.cpp index a2edf7b..d6e0cd5 100644 --- a/app/SettingsDialog.cpp +++ b/app/SettingsDialog.cpp @@ -24,12 +24,16 @@ using namespace std; -SettingsDialog::SettingsDialog(MainWindow *parent): +SettingsDialog::SettingsDialog(MainWindow *parent, QSettings *settings): m_device(""), m_parent(parent), m_buffersize(2048), - m_samplerate(44100) { + m_samplerate(44100), + m_settings(settings) { m_Ui.setupUi(this); + m_Ui.guiOSCLineEdit->setText(settings->value("gui_port").toByteArray().constData()); + m_Ui.processOSCLineEdit->setText(settings->value("process_port").toByteArray().constData()); + m_Ui.audioOSCLineEdit->setText(settings->value("audio_port").toByteArray().constData()); } void SettingsDialog::connect() { diff --git a/app/SettingsDialog.h b/app/SettingsDialog.h index df5248f..225e188 100644 --- a/app/SettingsDialog.h +++ b/app/SettingsDialog.h @@ -20,6 +20,7 @@ #include #include #include +#include #include "ui_settings.h" #include @@ -36,7 +37,7 @@ class SettingsDialog : public QDialog { Q_OBJECT public: - SettingsDialog(MainWindow *parent); + SettingsDialog(MainWindow *parent, QSettings *settings); Ui_SettingsDialog m_Ui; string m_device; @@ -61,15 +62,28 @@ class SettingsDialog : public QDialog void accept() { connect(); hide(); } void reject() { hide(); } void apply() { connect(); } - + + void gui_port(QString str) { + m_settings->setValue("gui_port",str); + } + + void process_port(QString str) { + m_settings->setValue("process_port",str); + } + + void audio_port(QString str) { + m_settings->setValue("audio_port",str); + } + private: - void connect(); - MainWindow *m_parent; + void connect(); + MainWindow *m_parent; - unsigned int m_buffersize; - unsigned int m_samplerate; + unsigned int m_buffersize; + unsigned int m_samplerate; + QSettings *m_settings; }; #endif diff --git a/app/qtmain.cpp b/app/qtmain.cpp index f2c5cdb..a0573c1 100644 --- a/app/qtmain.cpp +++ b/app/qtmain.cpp @@ -50,7 +50,7 @@ int main( int argc , char *argv[] ){ status::set_port(gui_port); - MainWindow mainWin(gui_port,audio_port,process_port); + MainWindow mainWin(gui_port,audio_port,process_port,&settings); mainWin.show(); process_thread pt(process_port); audio_thread at(audio_port,pt); diff --git a/brain/src/spiralcore/OSC_server.cpp b/brain/src/spiralcore/OSC_server.cpp index e37fa4a..523e17f 100644 --- a/brain/src/spiralcore/OSC_server.cpp +++ b/brain/src/spiralcore/OSC_server.cpp @@ -34,11 +34,12 @@ OSC_server::OSC_server(const string &port) : m_port(port), m_exit(false), m_command_ring_buffer(262144) { - //cerr<<"using port: ["< - samplebrain 0.18.4 + samplebrain 0.18.5 diff --git a/gui/settings.ui b/gui/settings.ui index 9d740c0..e8f8835 100644 --- a/gui/settings.ui +++ b/gui/settings.ui @@ -6,8 +6,8 @@ 0 0 - 400 - 522 + 438 + 656 @@ -31,7 +31,13 @@ - + + + + Comic Sans MS + + + @@ -57,6 +63,11 @@ 0 + + + Comic Sans MS + + 44100 @@ -73,7 +84,7 @@ - (note: this currently should probably match your sample file's input rate as no conversion is run on them - yet) + note: this currently should probably match your sample file's input rate as no conversion is run on them - yet true @@ -98,6 +109,11 @@ + + + Comic Sans MS + + 5 @@ -180,6 +196,112 @@ + + + + + Comic Sans MS + 8 + + + + restart needed for the ports below to update + + + + + + + + + + Comic Sans MS + + + + gui osc port + + + + + + + + 0 + 0 + + + + + Comic Sans MS + + + + + + + + + + + + + Comic Sans MS + + + + process osc port + + + + + + + + 0 + 0 + + + + + Comic Sans MS + + + + + + + + + + + + + Comic Sans MS + + + + audio osc port + + + + + + + + 0 + 0 + + + + + Comic Sans MS + + + + + + @@ -218,8 +340,8 @@ accept() - 231 - 358 + 244 + 642 157 @@ -234,8 +356,8 @@ reject() - 299 - 364 + 312 + 642 286 @@ -266,8 +388,8 @@ apply() - 112 - 321 + 125 + 592 4 @@ -298,8 +420,8 @@ buffersize(QString) - 326 - 131 + 390 + 215 395 @@ -307,11 +429,62 @@ + + guiOSCLineEdit + textChanged(QString) + SettingsDialog + gui_port(QString) + + + 346 + 432 + + + 401 + 419 + + + + + processOSCLineEdit + textChanged(QString) + SettingsDialog + process_port(QString) + + + 356 + 482 + + + 398 + 479 + + + + + audioOSCLineEdit + textChanged(QString) + SettingsDialog + audio_port(QString) + + + 374 + 511 + + + 398 + 517 + + + output_device(QString) samplerate(QString) buffersize(QString) apply() + gui_port(QString) + audio_port(QString) + process_port(QString) From e1c8e0ea67a1f1eb2da2d70a1a7f0a3cdcc5c20b Mon Sep 17 00:00:00 2001 From: Dave Griffiths Date: Fri, 14 Oct 2022 20:30:12 +0100 Subject: [PATCH 44/50] added more soundfile formats to the loading dialog and new error report when it can't load --- app/MainWindow.cpp | 3 ++- app/MainWindow.h | 40 +++++++++++++++++++--------------------- brain/src/brain.cpp | 2 ++ 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/app/MainWindow.cpp b/app/MainWindow.cpp index 5e0eb52..b7412ca 100644 --- a/app/MainWindow.cpp +++ b/app/MainWindow.cpp @@ -31,7 +31,8 @@ MainWindow::MainWindow(const string &port, const string &audio_port, const strin m_last_file("."), m_feedback(port), m_audio_port(audio_port), - m_process_port(process_port) + m_process_port(process_port), + m_format_string("Microsoft WAV (*.wav);;SGI/Apple AIFF (*.aiff);;SGI/Apple AIFC (*.aifc);;Sun/DEC/NeXT AU (*.au);;Sun/DEC/NeXT SND (*.snd);;Fasttracker 2 XI (*.xi);;All files (*.*)") { m_sound_item_enable_mapper = new QSignalMapper(this); m_sound_item_delete_mapper = new QSignalMapper(this); diff --git a/app/MainWindow.h b/app/MainWindow.h index 7500cdb..48ab6b5 100644 --- a/app/MainWindow.h +++ b/app/MainWindow.h @@ -206,11 +206,10 @@ private slots: void run_slot() {} void load_target() { - m_last_file=QFileDialog::getOpenFileName( - this, - QString("Select an wav file"), + m_last_file=QFileDialog::getOpenFileName(this, + QString("Select an audio file"), m_last_file, - QString("Sounds (*.wav)")); + m_format_string); send_process_osc("/load_target","s",m_last_file.toStdString().c_str()); } @@ -222,26 +221,25 @@ private slots: void fft_spectrum_size(int) {} void generate() { send_process_osc("/generate_brain",""); } void load_sound() { - m_last_file=QFileDialog::getOpenFileName( - this, + m_last_file=QFileDialog::getOpenFileName(this, QString("Select a wav file"), m_last_file, - QString("Sounds (*.wav)")); + m_format_string); - send_process_osc("/load_sample","s",m_last_file.toStdString().c_str()); - - sound_items::sound_item &si = m_sound_items.add(m_Ui.brain_contents, m_last_file.toStdString(),true); - - QObject::connect(si.m_enable, SIGNAL(clicked()), m_sound_item_enable_mapper, SLOT(map())); - m_sound_item_enable_mapper->setMapping(si.m_enable, si.m_id); - QObject::connect(si.m_del, SIGNAL(clicked()), m_sound_item_delete_mapper, SLOT(map())); - m_sound_item_delete_mapper->setMapping(si.m_del, si.m_id); + if (m_last_file!="") { + send_process_osc("/load_sample","s",m_last_file.toStdString().c_str()); + sound_items::sound_item &si = m_sound_items.add(m_Ui.brain_contents, m_last_file.toStdString(),true); + QObject::connect(si.m_enable, SIGNAL(clicked()), m_sound_item_enable_mapper, SLOT(map())); + m_sound_item_enable_mapper->setMapping(si.m_enable, si.m_id); + QObject::connect(si.m_del, SIGNAL(clicked()), m_sound_item_delete_mapper, SLOT(map())); + m_sound_item_delete_mapper->setMapping(si.m_del, si.m_id); + } } void load_sounds() { m_last_file=QFileDialog::getExistingDirectory(this, - QString("Select a directory"), + QString("Select a directory of wav files"), m_last_file); @@ -319,7 +317,7 @@ private slots: this, QString("Select a wav file"), m_last_file, - QString("Sounds (*.wav)")); + QString("Sounds (*.wav);;All files (*.*)")); m_save_wav = m_last_file.toStdString(); // chop off .wav size_t pos = m_save_wav.find_last_of("."); @@ -345,7 +343,7 @@ private slots: this, QString("Select a brain file"), m_last_file, - QString("Brains (*.brain)")); + QString("Brains (*.brain);;All files (*.*)")); send_process_osc("/load_brain","s",m_last_file.toStdString().c_str()); } @@ -354,7 +352,7 @@ private slots: this, QString("Select a brain file"), m_last_file, - QString("Brains (*.brain)")); + QString("Brains (*.brain);;All files (*.*)")); send_process_osc("/save_brain","s",m_last_file.toStdString().c_str()); } @@ -364,7 +362,7 @@ private slots: this, QString("Select a session file"), m_last_file, - QString("Sessions *.samplebrain (*.samplebrain)")); + QString("Sessions *.samplebrain (*.samplebrain);;All files (*.*)")); send_process_osc("/load_session","s",m_last_file.toStdString().c_str()); init_from_session(m_last_file.toStdString()); @@ -455,5 +453,5 @@ private: string m_audio_port; string m_process_port; - + QString m_format_string; }; diff --git a/brain/src/brain.cpp b/brain/src/brain.cpp index 6c63289..7625cb4 100644 --- a/brain/src/brain.cpp +++ b/brain/src/brain.cpp @@ -69,6 +69,8 @@ void brain::load_sound(std::string filename, stereo_mode mode) { delete[] temp; m_samples.push_back(sound(filename,s)); status::update("loaded %s",filename.c_str()); + } else { + status::update("problem loading %s",filename.c_str()); } } From 385563e64ef1cf0d1b2289126f01d5632a0ba9eb Mon Sep 17 00:00:00 2001 From: Dave Griffiths Date: Fri, 14 Oct 2022 21:24:56 +0100 Subject: [PATCH 45/50] forgot to add flac --- app/MainWindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/MainWindow.cpp b/app/MainWindow.cpp index b7412ca..623f583 100644 --- a/app/MainWindow.cpp +++ b/app/MainWindow.cpp @@ -32,7 +32,7 @@ MainWindow::MainWindow(const string &port, const string &audio_port, const strin m_feedback(port), m_audio_port(audio_port), m_process_port(process_port), - m_format_string("Microsoft WAV (*.wav);;SGI/Apple AIFF (*.aiff);;SGI/Apple AIFC (*.aifc);;Sun/DEC/NeXT AU (*.au);;Sun/DEC/NeXT SND (*.snd);;Fasttracker 2 XI (*.xi);;All files (*.*)") + m_format_string("Microsoft WAV (*.wav);;SGI/Apple AIFF (*.aiff);;SGI/Apple AIFC (*.aifc);;Sun/DEC/NeXT AU (*.au);;Sun/DEC/NeXT SND (*.snd);;Fasttracker 2 XI (*.xi);;Free Lossless Audio Codec FLAC (*.flac);;All files (*.*)") { m_sound_item_enable_mapper = new QSignalMapper(this); m_sound_item_delete_mapper = new QSignalMapper(this); From 7bc12ac0be0ce8887b1537c137b3c349259dd58d Mon Sep 17 00:00:00 2001 From: Dave Griffiths Date: Fri, 14 Oct 2022 22:18:52 +0100 Subject: [PATCH 46/50] cmake build fixes --- CMakeLists.txt | 28 ++++++++++++++-------------- app/MainWindow.h | 2 +- app/SettingsDialog.h | 2 +- {gui => app/gui}/buttons.svg | 0 {gui => app/gui}/samplebrain.ui | 0 {gui => app/gui}/settings.ui | 0 6 files changed, 16 insertions(+), 16 deletions(-) rename {gui => app/gui}/buttons.svg (100%) rename {gui => app/gui}/samplebrain.ui (100%) rename {gui => app/gui}/settings.ui (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4bdb307..3fbc382 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(QT NAMES Qt5 Qt6 REQUIRED COMPONENTS Core) find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Gui Widgets) -qt_add_executable(samplebrain WIN32 MACOSX_BUNDLE +add_executable(samplebrain WIN32 MACOSX_BUNDLE app/MainWindow.cpp app/MainWindow.h app/SettingsDialog.cpp app/SettingsDialog.h app/audio_thread.cpp @@ -46,8 +46,8 @@ qt_add_executable(samplebrain WIN32 MACOSX_BUNDLE brain/src/spiralcore/stream.cpp brain/src/status.cpp brain/src/window.cpp - gui/samplebrain.ui - gui/settings.ui + app/gui/samplebrain.ui + app/gui/settings.ui ) target_include_directories(samplebrain PRIVATE . @@ -55,9 +55,9 @@ target_include_directories(samplebrain PRIVATE ) target_link_libraries(samplebrain PRIVATE - Qt::Core - Qt::Gui - Qt::Widgets + Qt5::Core + Qt5::Gui + Qt5::Widgets fftw3 lo_shared portaudio @@ -74,14 +74,14 @@ set(samplebrain_resource_files "app/images/stop.png" ) -qt_add_resources(samplebrain "samplebrain" - PREFIX - "/images" - BASE - "app" - FILES - ${samplebrain_resource_files} -) +#qt5_add_resources(samplebrain "samplebrain" +# PREFIX +# "/images" +# BASE +# "app" +# FILES +# ${samplebrain_resource_files} +#) install(TARGETS samplebrain BUNDLE DESTINATION . diff --git a/app/MainWindow.h b/app/MainWindow.h index 48ab6b5..9b97127 100644 --- a/app/MainWindow.h +++ b/app/MainWindow.h @@ -19,7 +19,7 @@ #include #include #include -#include "ui_samplebrain.h" +#include "gui/ui_samplebrain.h" #include "SettingsDialog.h" #include diff --git a/app/SettingsDialog.h b/app/SettingsDialog.h index 225e188..ebb9666 100644 --- a/app/SettingsDialog.h +++ b/app/SettingsDialog.h @@ -21,7 +21,7 @@ #include #include #include -#include "ui_settings.h" +#include "gui/ui_settings.h" #include #include diff --git a/gui/buttons.svg b/app/gui/buttons.svg similarity index 100% rename from gui/buttons.svg rename to app/gui/buttons.svg diff --git a/gui/samplebrain.ui b/app/gui/samplebrain.ui similarity index 100% rename from gui/samplebrain.ui rename to app/gui/samplebrain.ui diff --git a/gui/settings.ui b/app/gui/settings.ui similarity index 100% rename from gui/settings.ui rename to app/gui/settings.ui From b97143510111f3467181706594fad1df628059e3 Mon Sep 17 00:00:00 2001 From: Dave Griffiths Date: Fri, 14 Oct 2022 22:25:38 +0100 Subject: [PATCH 47/50] cmake qt fiddling --- CMakeLists.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3fbc382..060db99 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,8 +15,7 @@ set(CMAKE_AUTOUIC ON) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) -find_package(QT NAMES Qt5 Qt6 REQUIRED COMPONENTS Core) -find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Gui Widgets) +find_package(Qt5 REQUIRED COMPONENTS Core Gui Widgets) add_executable(samplebrain WIN32 MACOSX_BUNDLE app/MainWindow.cpp app/MainWindow.h From af44ac43f08c194cab654e9b6f163e7f11f72e8e Mon Sep 17 00:00:00 2001 From: Dave Griffiths Date: Fri, 14 Oct 2022 22:29:25 +0100 Subject: [PATCH 48/50] more cmake qt fiddling --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 060db99..7cdc199 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,7 +15,8 @@ set(CMAKE_AUTOUIC ON) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) -find_package(Qt5 REQUIRED COMPONENTS Core Gui Widgets) +find_package(QT NAMES Qt5 REQUIRED COMPONENTS Core) +find_package(Qt5 REQUIRED COMPONENTS Gui Widgets) add_executable(samplebrain WIN32 MACOSX_BUNDLE app/MainWindow.cpp app/MainWindow.h From 1d03e9adddec0e15a6b09f5e343567d5f7bd624d Mon Sep 17 00:00:00 2001 From: Dave Griffiths Date: Fri, 14 Oct 2022 22:33:17 +0100 Subject: [PATCH 49/50] ci tweak --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index cf81e47..bd6c18b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,7 +3,7 @@ build-job: image: ubuntu:22.04 script: - apt-get update - - apt-get install -y git cmake g++ freeglut3-dev qt6-base-dev + - apt-get install -y git cmake g++ freeglut3-dev qtbase5-dev qt5-qmake qtbase5-dev-tools - mkdir -p build - cd build - cmake .. From 884c8ad91f7bae2144d82814d9a571f53e20f760 Mon Sep 17 00:00:00 2001 From: Dave Griffiths Date: Thu, 20 Oct 2022 21:12:58 +0100 Subject: [PATCH 50/50] resources location --- CMakeLists.txt | 12 ++++++------ app/gui/samplebrain.ui | 13 +++++++------ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7cdc199..71a5d5e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,14 +74,14 @@ set(samplebrain_resource_files "app/images/stop.png" ) -#qt5_add_resources(samplebrain "samplebrain" -# PREFIX -# "/images" +qt5_add_resources(samplebrain "samplebrain" + PREFIX + "/images" # BASE # "app" -# FILES -# ${samplebrain_resource_files} -#) + FILES + ${samplebrain_resource_files} +) install(TARGETS samplebrain BUNDLE DESTINATION . diff --git a/app/gui/samplebrain.ui b/app/gui/samplebrain.ui index 45d8d2c..e3626eb 100644 --- a/app/gui/samplebrain.ui +++ b/app/gui/samplebrain.ui @@ -1466,7 +1466,7 @@ - + :/images/images/play.png:/images/images/play.png @@ -1493,7 +1493,7 @@ - + :/images/images/pause.png:/images/images/pause.png @@ -1513,7 +1513,7 @@ - + :/images/images/record.png:/images/images/record.png @@ -1533,7 +1533,7 @@ - + :/images/images/stop.png:/images/images/stop.png @@ -1599,7 +1599,7 @@ - + :/images/images/settings.png:/images/images/settings.png @@ -1632,7 +1632,7 @@ - :/images/images/at.png + :/images/images/at.png @@ -1643,6 +1643,7 @@ +