├── dist ├── logo-48.png ├── qfxgen.desktop └── sfxgen.spec ├── test ├── pulse5_no.rfx ├── pulse5_pn.rfx ├── pulse5_sa.rfx ├── pulse5_si.rfx ├── pulse5_sq.rfx ├── pulse5_tr.rfx ├── sa_enchant.rfx ├── si_blink.rfx ├── sq_default.rfx ├── test.sh └── wav.sha1 ├── gui_qt ├── version.h ├── icons.qrc ├── icons │ ├── wave-pinknoise.svg │ ├── wave-square.svg │ ├── wave-triangle.svg │ ├── wave-sawtooth.svg │ ├── wave-noise.svg │ └── wave-sine.svg ├── SfxWindow.h ├── FilesModel.cpp └── SfxWindow.cpp ├── support ├── saveWave.h ├── well512.h ├── audio.h ├── well512.c ├── saveWave.c └── audio_openal.c ├── qfxgen.pro ├── COPYING ├── project.b ├── cbuild ├── main.c ├── README.md ├── sfx_gen.h └── sfx_gen.c /dist/logo-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WickedSmoke/sfx_gen/HEAD/dist/logo-48.png -------------------------------------------------------------------------------- /test/pulse5_no.rfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WickedSmoke/sfx_gen/HEAD/test/pulse5_no.rfx -------------------------------------------------------------------------------- /test/pulse5_pn.rfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WickedSmoke/sfx_gen/HEAD/test/pulse5_pn.rfx -------------------------------------------------------------------------------- /test/pulse5_sa.rfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WickedSmoke/sfx_gen/HEAD/test/pulse5_sa.rfx -------------------------------------------------------------------------------- /test/pulse5_si.rfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WickedSmoke/sfx_gen/HEAD/test/pulse5_si.rfx -------------------------------------------------------------------------------- /test/pulse5_sq.rfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WickedSmoke/sfx_gen/HEAD/test/pulse5_sq.rfx -------------------------------------------------------------------------------- /test/pulse5_tr.rfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WickedSmoke/sfx_gen/HEAD/test/pulse5_tr.rfx -------------------------------------------------------------------------------- /test/sa_enchant.rfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WickedSmoke/sfx_gen/HEAD/test/sa_enchant.rfx -------------------------------------------------------------------------------- /test/si_blink.rfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WickedSmoke/sfx_gen/HEAD/test/si_blink.rfx -------------------------------------------------------------------------------- /test/sq_default.rfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WickedSmoke/sfx_gen/HEAD/test/sq_default.rfx -------------------------------------------------------------------------------- /gui_qt/version.h: -------------------------------------------------------------------------------- 1 | #ifndef VERSION_H 2 | #define VERSION_H 3 | 4 | #define APP_NAME "qFXGen" 5 | #define APP_VERSION "0.6.0" 6 | 7 | #endif // VERSION_H 8 | -------------------------------------------------------------------------------- /test/test.sh: -------------------------------------------------------------------------------- 1 | # sfxgen regression test 2 | 3 | if [ "$1" = "update" ]; then 4 | sha1sum *.wav >wav.sha1 5 | else 6 | ../sfxgen *.rfx 7 | sha1sum -c wav.sha1 8 | fi 9 | -------------------------------------------------------------------------------- /dist/qfxgen.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Application 3 | Name=qfxgen 4 | Comment=Sound effect generator 5 | Exec=qfxgen 6 | Icon=qfxgen 7 | Terminal=0 8 | Categories=Audio;AudioVideo;Qt 9 | -------------------------------------------------------------------------------- /gui_qt/icons.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | icons/wave-square.svg 4 | icons/wave-sawtooth.svg 5 | icons/wave-sine.svg 6 | icons/wave-noise.svg 7 | icons/wave-triangle.svg 8 | icons/wave-pinknoise.svg 9 | 10 | 11 | -------------------------------------------------------------------------------- /gui_qt/icons/wave-pinknoise.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /gui_qt/icons/wave-square.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /gui_qt/icons/wave-triangle.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /support/saveWave.h: -------------------------------------------------------------------------------- 1 | #ifndef SAVEWAVE_H 2 | #define SAVEWAVE_H 3 | 4 | #include 5 | 6 | #ifdef __cplusplus 7 | extern "C" 8 | #endif 9 | const char* saveWave(const void* data, uint32_t dataSize, 10 | int sampleRate, int bitsPerSample, int channels, 11 | const char* filename); 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /gui_qt/icons/wave-sawtooth.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /gui_qt/icons/wave-noise.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /qfxgen.pro: -------------------------------------------------------------------------------- 1 | OBJECTS_DIR = obj 2 | MOC_DIR = moc 3 | 4 | QT += widgets 5 | RESOURCES += gui_qt/icons.qrc 6 | 7 | CONFIG += qt 8 | #CONFIG += debug 9 | 10 | INCLUDEPATH += gui_qt support 11 | LIBS += -lopenal 12 | 13 | HEADERS += gui_qt/SfxWindow.h sfx_gen.h 14 | SOURCES += gui_qt/SfxWindow.cpp sfx_gen.c 15 | SOURCES += support/audio_openal.c support/saveWave.c support/well512.c 16 | -------------------------------------------------------------------------------- /gui_qt/icons/wave-sine.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | The sfx_gen C library and the CLI program may be used under the terms of the 2 | MIT license (see sfx_gen.c). 3 | 4 | The GUI program in the gui_qt directory is licensed under the GPLv3 (see 5 | https://www.gnu.org/licenses/gpl-3.0.txt). 6 | 7 | The SVG icons are from https://icon-sets.iconify.design/?query=wave and 8 | licensed as follows: 9 | wave-noise.svg: Copyright Austin Andrews, Apache 2.0 license. 10 | wave-[st]*.svg: Copyright Phosphor Icons, MIT license. 11 | -------------------------------------------------------------------------------- /test/wav.sha1: -------------------------------------------------------------------------------- 1 | f58386c37a1623c39d8ce5f7b2848cb268ecf106 pulse5_no.wav 2 | be8be390cff7262d59ccde006e9980ca448bef89 pulse5_pn.wav 3 | e8499837c80b2d10f5d40fda6de94742d41092dc pulse5_sa.wav 4 | f15bf85bddf5c8b0c22f4420ad662dc4b1f4e327 pulse5_si.wav 5 | e9edca85fc40defaa265b268f6ec1c45cd3df1d4 pulse5_sq.wav 6 | 98c290d158571e21f421bffba5c8ff9f974824fa pulse5_tr.wav 7 | 422b846352e17d6d45f95f8617ee7f5a37e8b43d sa_enchant.wav 8 | 27cdee235062666d845e96fe30457cd207c1db3d si_blink.wav 9 | 07fad1966f486c6d4f4335b8544838534e273a8e sq_default.wav 10 | -------------------------------------------------------------------------------- /support/well512.h: -------------------------------------------------------------------------------- 1 | #ifndef WELL512_H 2 | #define WELL512_H 3 | 4 | #ifdef __sun__ 5 | #include 6 | #else 7 | #include 8 | #endif 9 | 10 | #define WELL512_STATE_SIZE 16 11 | 12 | typedef struct 13 | { 14 | uint32_t wi; 15 | uint32_t wstate[ WELL512_STATE_SIZE ]; 16 | } 17 | Well512; 18 | 19 | 20 | #ifdef __cplusplus 21 | extern "C" { 22 | #endif 23 | 24 | void well512_init( Well512* ws, uint32_t seed ); 25 | uint32_t well512_genU32( Well512* ws ); 26 | 27 | #ifdef __cplusplus 28 | } 29 | #endif 30 | 31 | 32 | /* 33 | Generate a random number on [0,1)-real-interval. 34 | (uint32_t divided by 2^32) 35 | */ 36 | #define well512_genReal(ws) (well512_genU32(ws) * (1.0 / 4294967296.0)) 37 | 38 | 39 | #endif /* WELL512_H */ 40 | -------------------------------------------------------------------------------- /project.b: -------------------------------------------------------------------------------- 1 | options [ 2 | audio-api: 'faun "Audio interface ('faun or 'openal)" 3 | ] 4 | 5 | default [ 6 | include_from [%. %support] 7 | ] 8 | 9 | exe %qfxgen [ 10 | qt [widgets] 11 | include_from %gui_qt 12 | sources [ 13 | %gui_qt/SfxWindow.cpp 14 | %gui_qt/icons.qrc 15 | %sfx_gen.c 16 | %support/saveWave.c 17 | %support/well512.c 18 | ] 19 | either eq? audio-api 'faun [ 20 | cflags "-DUSE_FAUN" 21 | libs %faun 22 | ][ 23 | sources [ 24 | %support/audio_openal.c 25 | ] 26 | linux [libs %openal] 27 | macx [lflags "-framework OpenAL"] 28 | win32 [libs %OpenAL32.dll] 29 | ] 30 | ] 31 | 32 | exe %sfxgen [ 33 | console 34 | sources [%main.c] 35 | unix [libs %m] 36 | ] 37 | -------------------------------------------------------------------------------- /support/audio.h: -------------------------------------------------------------------------------- 1 | #ifndef AUDIO_H 2 | #define AUDIO_H 3 | /* 4 | Audio Module 5 | Copyright 2005-2012,2022 Karl Robillard 6 | 7 | This code may be used under the terms of the MIT license (see audio_openal.c). 8 | */ 9 | 10 | #include 11 | 12 | #ifdef __cplusplus 13 | extern "C" { 14 | #endif 15 | 16 | int aud_startup(); 17 | void aud_shutdown(); 18 | void aud_stopAll(); 19 | void aud_pauseProcessing(int paused); 20 | void aud_genBuffers(int count, uint32_t* ids); 21 | void aud_freeBuffers(int count, uint32_t* ids); 22 | int aud_loadBufferI16(uint32_t bufId, const int16_t* samples, int sampleCount, 23 | int stereo, int freq); 24 | int aud_loadBufferF32(uint32_t bufId, const float* samples, int sampleCount, 25 | int stereo, int freq); 26 | uint32_t aud_playSound(uint32_t bufferId); 27 | void aud_stopSound(uint32_t sourceId); 28 | void aud_setSoundVolume(float); 29 | 30 | #ifdef __cplusplus 31 | } 32 | #endif 33 | 34 | #endif /* AUDIO_H */ 35 | -------------------------------------------------------------------------------- /support/well512.c: -------------------------------------------------------------------------------- 1 | /* 2 | * WELL 512 Random number generator 3 | * See http://www.iro.umontreal.ca/~panneton/WELLRNG.html 4 | */ 5 | 6 | 7 | #include "well512.h" 8 | 9 | 10 | void well512_init( Well512* ws, uint32_t seed ) 11 | { 12 | int i; 13 | uint32_t prev; 14 | uint32_t* wstate = ws->wstate; 15 | 16 | ws->wi = 0; 17 | wstate[0] = seed; 18 | for( i = 1; i < WELL512_STATE_SIZE; ++i ) 19 | { 20 | prev = wstate[i-1]; 21 | wstate[i] = (1812433253 * (prev ^ (prev >> 30)) + i); 22 | } 23 | } 24 | 25 | 26 | /* 27 | Generates a random number on [0,0xffffffff]-interval 28 | */ 29 | uint32_t well512_genU32( Well512* ws ) 30 | { 31 | uint32_t a, b, c, z0; 32 | uint32_t* wstate = ws->wstate; 33 | uint32_t wi = ws->wi; 34 | 35 | #define MAT0(v,t) (v^(v<>11); 43 | 44 | wstate[wi] = a = b ^ c; 45 | ws->wi = wi = (wi + 15) & 15; 46 | z0 = wstate[wi]; 47 | wstate[wi] = MAT0(z0,2) ^ MAT0(b,18) ^ (c<<28) ^ 48 | (a ^ ((a << 5) & 0xDA442D24)); 49 | 50 | return wstate[wi]; 51 | } 52 | -------------------------------------------------------------------------------- /dist/sfxgen.spec: -------------------------------------------------------------------------------- 1 | Summary: Sound Effect Generator 2 | Name: sfxgen 3 | Version: 0.6.0 4 | Release: %autorelease 5 | License: GPL-3.0-or-later 6 | URL: https://codeberg.org/wickedsmoke/sfx_gen 7 | Source: https://codeberg.org/wickedsmoke/sfx_gen/archive/v%{version}.tar.gz 8 | BuildRequires: gcc-c++ qt6-qtbase-devel openal-soft-devel 9 | 10 | %global debug_package %{nil} 11 | 12 | %description 13 | Sfxgen is a sound effect generator based on DrPetter's sfxr project. 14 | A Qt based GUI program (qfxgen) and a CLI program (sfxgen) are both included 15 | in this package. 16 | 17 | %prep 18 | %setup -q -n sfx_gen 19 | 20 | %build 21 | cc main.c -O3 -Isupport -lm -o sfxgen 22 | qmake6 qfxgen.pro 23 | %make_build 24 | 25 | %install 26 | mkdir -p %{buildroot}%{_bindir} %{buildroot}%{_datadir}/applications 27 | install -m 755 qfxgen %{buildroot}%{_bindir} 28 | install -m 755 sfxgen %{buildroot}%{_bindir} 29 | install -D -m 644 dist/logo-48.png %{buildroot}%{_datadir}/icons/hicolor/48x48/apps/qfxgen.png 30 | install -D -m 644 dist/qfxgen.desktop %{buildroot}%{_datadir}/applications 31 | 32 | %clean 33 | rm -rf $RPM_BUILD_ROOT 34 | 35 | %files 36 | %license COPYING 37 | %defattr(-,root,root) 38 | %{_bindir}/qfxgen 39 | %{_bindir}/sfxgen 40 | %{_datadir}/icons/hicolor/48x48/apps/qfxgen.png 41 | %{_datadir}/applications/qfxgen.desktop 42 | 43 | %changelog 44 | * Fri Aug 9 2024 Karl Robillard 0.6.0 45 | - Initial package release. 46 | -------------------------------------------------------------------------------- /cbuild: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | # Container build script for qfxgen. 3 | 4 | if [ ! -s project.tar.gz ]; then 5 | copr -a 6 | fi 7 | 8 | TIME=$(date +%H%M%S) 9 | SCRIPT=build-$TIME 10 | ID=${SCRIPT} 11 | HDIR=/tmp 12 | CDIR=/tmp 13 | ARC_DIR=/tmp/qfxgen-0.5 14 | 15 | clean_dir () { 16 | if [ -d "$1" ]; then rm -rf "$1"/*; else mkdir -p "$1"; fi 17 | } 18 | 19 | container_build () { 20 | clean_dir ${ARC_DIR} 21 | 22 | podman run -d -it --name=$ID $1 /bin/bash || exit 23 | podman cp project.tar.gz $ID:$CDIR 24 | podman cp $HDIR/${SCRIPT} $ID:$CDIR/${SCRIPT} 25 | podman exec -it $ID /bin/bash $CDIR/${SCRIPT} 26 | } 27 | 28 | case $1 in 29 | windows) 30 | echo ' 31 | mkdir qfxgen 32 | cd qfxgen 33 | tar xf /tmp/project.tar.gz 34 | copr -t mingw 35 | ' >$HDIR/${SCRIPT} 36 | 37 | container_build dev/f35-mingw 38 | podman cp $ID:/home/build/qfxgen/qfxgen.exe ${ARC_DIR} 39 | podman cp $ID:/home/build/qfxgen/sfxgen.exe ${ARC_DIR} 40 | 41 | # Build zip archive. 42 | if [ "$2" != "-b" ]; then 43 | FN=`readlink -f arc/mingw-qt_app.tar.gz` 44 | tar xf $FN -C ${ARC_DIR} --strip-components=1 45 | cd ${ARC_DIR%/*}; zip -r qfxgen-0.5.zip ${ARC_DIR##*/} 46 | fi 47 | ;; 48 | 49 | linux) 50 | echo ' 51 | mkdir qfxgen 52 | cd qfxgen 53 | tar xf /tmp/project.tar.gz 54 | copr 55 | ' >$HDIR/${SCRIPT} 56 | 57 | container_build dev/f35 58 | podman cp $ID:/home/build/qfxgen/qfxgen ${ARC_DIR} 59 | podman cp $ID:/home/build/qfxgen/sfxgen ${ARC_DIR} 60 | ;; 61 | 62 | *) 63 | echo "Usage: $0 {linux|windows} [-b]" 64 | echo -e '\nOptions:' 65 | echo ' -b Build binary only; do not create archive.' 66 | exit 1 67 | esac 68 | 69 | echo "$SCRIPT done!" 70 | podman stop $ID 71 | -------------------------------------------------------------------------------- /support/saveWave.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | /* 5 | * Save PCM data to WAVE file. 6 | * Return error message or NULL if successful. 7 | */ 8 | const char* saveWave(const void* data, uint32_t dataSize, 9 | int sampleRate, int bitsPerSample, int channels, 10 | const char* filename) 11 | { 12 | uint32_t dword; 13 | uint16_t word; 14 | uint32_t bytesPerSec = sampleRate * bitsPerSample/8 * channels; 15 | const char* err = "File write failed"; 16 | FILE* fp = fopen(filename, "wb"); 17 | 18 | if(! fp) 19 | return "File open failed"; 20 | 21 | #define WRITE_ID(STR) \ 22 | if (fwrite(STR, 4, 1, fp) != 1) \ 23 | goto cleanup 24 | 25 | #define WRITE_32(N) \ 26 | dword = N; \ 27 | fwrite(&dword, 1, 4, fp) 28 | 29 | #define WRITE_16(N) \ 30 | word = N; \ 31 | fwrite(&word, 1, 2, fp) 32 | 33 | // Write WAVE header. 34 | WRITE_ID("RIFF"); // "RIFF" 35 | WRITE_32(36 + dataSize); // Remaining file size 36 | WRITE_ID("WAVE"); // "WAVE" 37 | 38 | WRITE_ID("fmt "); // "fmt " 39 | WRITE_32(16); // Chunk size 40 | WRITE_16(1); // Compression code 41 | WRITE_16(channels); // Channels 42 | WRITE_32(sampleRate); // Sample rate 43 | WRITE_32(bytesPerSec); // Bytes/sec 44 | WRITE_16(bitsPerSample/8); // Block align 45 | WRITE_16(bitsPerSample); // Bits per sample 46 | 47 | // Write sample data. 48 | WRITE_ID("data"); // "data" 49 | WRITE_32(dataSize); // Chunk size 50 | if (fwrite(data, 1, dataSize, fp) == dataSize) 51 | err = NULL; 52 | 53 | cleanup: 54 | fclose(fp); 55 | return err; 56 | } 57 | -------------------------------------------------------------------------------- /gui_qt/SfxWindow.h: -------------------------------------------------------------------------------- 1 | #ifndef SFX_WINDOW_H 2 | #define SFX_WINDOW_H 3 | /* 4 | sfx_gen Qt GUI 5 | 6 | Copyright 2022 Karl Robillard 7 | 8 | This program may be used under the terms of the GPLv3 license 9 | (see SfxWindow.cpp). 10 | */ 11 | 12 | 13 | #include 14 | 15 | #define PARAM_VOL 0 16 | #define PARAM_COUNT 23 17 | 18 | struct SfxParams; 19 | struct SfxSynth; 20 | struct Wave; 21 | struct WaveTables; 22 | class FilesModel; 23 | class QBoxLayout; 24 | class QGridLayout; 25 | class QLabel; 26 | class QPushButton; 27 | class QSlider; 28 | 29 | class SfxWindow : public QMainWindow 30 | { 31 | Q_OBJECT 32 | 33 | public: 34 | 35 | SfxWindow(); 36 | ~SfxWindow(); 37 | bool open(const QString& file, bool updateList); 38 | 39 | public slots: 40 | 41 | void regenerate(bool play); 42 | void showAbout(); 43 | 44 | protected: 45 | 46 | virtual void closeEvent( QCloseEvent* ); 47 | 48 | private slots: 49 | 50 | void open(); 51 | void save(); 52 | void saveAs(); 53 | void copy(); 54 | void paste(); 55 | void resetParam(); 56 | void setPoc(bool on); 57 | void playSound(); 58 | void generateSound(); 59 | void mutate(); 60 | void randomize(); 61 | void chooseWaveSlot(int, bool checked); 62 | void chooseWaveForm(int, bool checked); 63 | void chooseFile(const QModelIndex&); 64 | void volumeChanged(int); 65 | void paramChanged(int); 66 | 67 | private: 68 | 69 | void createActions(); 70 | void createMenus(); 71 | void createTools(); 72 | void addSlider(QGridLayout* grid, int row, const char* label, int low); 73 | void layoutGenerators(QBoxLayout*); 74 | void layoutParams(QBoxLayout*); 75 | void updateParameterWidgets(const SfxParams*); 76 | void updateStats(const Wave*); 77 | void setProjectFile(const QString&); 78 | bool saveWaveFile(const Wave*, const QString&); 79 | bool saveRfx(const QString&); 80 | 81 | QAction* _actOpen; 82 | QAction* _actSave; 83 | QAction* _actSaveAs; 84 | QAction* _actPaste; 85 | QAction* _actPlay; 86 | QAction* _actPoc; 87 | 88 | QToolBar* _tools; 89 | QPushButton* _waveType[6]; 90 | QSlider* _param[PARAM_COUNT]; 91 | QLabel* _paramReadout[PARAM_COUNT]; 92 | QLabel* _wavePic; 93 | QPixmap _wavePix; 94 | QLabel* _stats[3]; 95 | FilesModel* _files; 96 | 97 | QString _prevProjPath; 98 | SfxSynth* _synth; 99 | WaveTables* _wav; 100 | int _activeWav; 101 | int _recentPid; 102 | bool _paramAssign; 103 | bool _playOnChange; 104 | 105 | // Disabled copy constructor and operator= 106 | SfxWindow( const SfxWindow & ) : QMainWindow( 0 ) {} 107 | SfxWindow &operator=( const SfxWindow & ) { return *this; } 108 | }; 109 | 110 | 111 | #endif //AWINDOW_H 112 | -------------------------------------------------------------------------------- /gui_qt/FilesModel.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | class FilesModel : public QAbstractItemModel 5 | { 6 | public: 7 | 8 | FilesModel(QObject* parent = nullptr) : QAbstractItemModel(parent) {} 9 | void setDirectory(const QString& path, const QString& filter = QString()); 10 | 11 | QString filePath(const QModelIndex& index) { 12 | quintptr id = index.internalId(); 13 | if (id < (unsigned long) _files.size()) 14 | return _directory + _files[id]; 15 | return QString(); 16 | } 17 | 18 | int columnCount(const QModelIndex& /*parent*/) const { 19 | return 1; 20 | } 21 | 22 | int rowCount(const QModelIndex& parent) const { 23 | return parent.isValid() ? 0 : _files.size(); 24 | } 25 | 26 | QVariant data(const QModelIndex& index, int role) const { 27 | if (role == Qt::DisplayRole) { 28 | quintptr id = index.internalId(); 29 | if (index.column() == 0 && id < (unsigned long) _files.size()) 30 | return _files[id]; 31 | } 32 | return QVariant(); 33 | } 34 | 35 | QVariant headerData(int section, Qt::Orientation, int role) const { 36 | if (section == 0 && role == Qt::DisplayRole) 37 | return "File Name"; 38 | return QVariant(); 39 | } 40 | 41 | QModelIndex index(int row, int column, const QModelIndex& parent) const { 42 | if (parent.isValid()) 43 | return QModelIndex(); 44 | return createIndex(row, column, quintptr(row)); 45 | } 46 | 47 | QModelIndex parent(const QModelIndex& /*index*/) const { 48 | return QModelIndex(); 49 | } 50 | 51 | //Qt::ItemFlags flags(const QModelIndex& index ) const; 52 | //bool removeRows( int row, int count, const QModelIndex& parent ); 53 | 54 | protected: 55 | QString _directory; 56 | QStringList _files; 57 | }; 58 | 59 | 60 | void FilesModel::setDirectory(const QString& path, const QString& filter) 61 | { 62 | _directory = path; 63 | if (! path.isEmpty() && path.back() != QDir::separator()) 64 | _directory.push_back(QDir::separator()); 65 | 66 | QDir dir(path, filter, QDir::Name, QDir::Files); 67 | QFileInfoList list = dir.entryInfoList(); 68 | if (list.empty() && _files.empty()) 69 | return; 70 | 71 | beginResetModel(); 72 | _files.clear(); 73 | for (int i = 0; i < list.size(); ++i) { 74 | _files.push_back( list.at(i).fileName() ); 75 | } 76 | endResetModel(); 77 | } 78 | 79 | 80 | #ifdef UNIT_TEST 81 | // g++ -DUNIT_TEST FilesModel.cpp -lQt5Core -lQt5Gui -lQt5Widgets 82 | // -I/usr/include/qt5 -I/usr/include/qt5/QtCore -I/usr/include/qt5/QtWidgets 83 | #include 84 | #include 85 | int main(int argc, char** argv) 86 | { 87 | QApplication app(argc, argv); 88 | 89 | QListView w; 90 | FilesModel* files = new FilesModel(&w); 91 | w.setModel(files); 92 | w.show(); 93 | files->setDirectory("/tmp", "*.wav"); 94 | 95 | return app.exec(); 96 | } 97 | #endif 98 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * sfx_gen CLI program 3 | * 4 | * Compile with: cc main.c -Isupport -lm -o sfxgen 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #define SINGLE_FORMAT 2 12 | #include "sfx_gen.c" 13 | #include "saveWave.c" 14 | 15 | #define EX_USAGE 64 /* command line usage error */ 16 | #define EX_IOERR 74 /* input/output error */ 17 | #define EX_CONFIG 78 /* configuration error */ 18 | 19 | #if 1 20 | #include "well512.c" 21 | Well512 rng; 22 | #define SEED_RNG(S) well512_init(&rng, S) 23 | #else 24 | #define SEED_RNG(S) srand(S) 25 | #endif 26 | 27 | int sfx_random(int range) 28 | { 29 | #ifdef WELL512_H 30 | return well512_genU32(&rng) % range; 31 | #else 32 | return rand() % range; 33 | #endif 34 | } 35 | 36 | // Copy file path and change extension. 37 | void copyPathExt(char* dest, const char* src, const char* ext) 38 | { 39 | char* lastDot = NULL; 40 | int ch; 41 | while ((ch = *src++)) { 42 | if (ch == '.') 43 | lastDot = dest; 44 | *dest++ = ch; 45 | } 46 | 47 | if (lastDot) 48 | dest = (char*) lastDot; 49 | while (*ext) 50 | *dest++ = *ext++; 51 | *dest = '\0'; 52 | } 53 | 54 | int main(int argc, char** argv) 55 | { 56 | SfxSynth* synth; 57 | SfxParams wp; 58 | char* pathBuf; 59 | const char* paramFile; 60 | const char* wavFile; 61 | const char* err; 62 | int i, scount; 63 | 64 | 65 | if (argc < 2) { 66 | printf("Usage: %s [-o ] ...\n", argv[0]); 67 | return EX_USAGE; 68 | } 69 | 70 | SEED_RNG(time(NULL)); 71 | synth = sfx_allocSynth(SFX_I16, 44100, 10); 72 | pathBuf = malloc(1024); 73 | 74 | for (i = 1; i < argc; ++i) { 75 | // Load Parameters. 76 | paramFile = argv[i]; 77 | err = sfx_loadParams(&wp, paramFile, NULL); 78 | if (err) { 79 | fprintf(stderr, "ERROR: %s (%s)\n", err, paramFile); 80 | return EX_CONFIG; 81 | } 82 | 83 | // Generate Sound. 84 | if (wp.randSeed) 85 | SEED_RNG(wp.randSeed); 86 | scount = sfx_generateWave(synth, &wp); 87 | 88 | // Save as WAVE. 89 | if (i+1 < argc && strcmp(argv[i+1], "-o") == 0) { 90 | i += 2; 91 | if (i < argc) 92 | wavFile = argv[i]; 93 | else { 94 | fprintf(stderr, "ERROR: Output filename missing\n"); 95 | return EX_USAGE; 96 | } 97 | } else { 98 | copyPathExt(pathBuf, paramFile, ".wav"); 99 | wavFile = pathBuf; 100 | } 101 | err = saveWave(synth->samples.i16, scount * sizeof(int16_t), 102 | synth->sampleRate, 16, 1, wavFile); 103 | if (err) { 104 | fprintf(stderr, "ERROR: %s (%s)\n", err, wavFile); 105 | return EX_IOERR; 106 | } 107 | } 108 | 109 | free(synth); 110 | free(pathBuf); 111 | return 0; 112 | } 113 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | sfx_gen 2 | ======= 3 | 4 | Sfx_gen is a stand-alone C version of DrPetter's [sfxr] sound effect generator. 5 | The project includes the following: 6 | 7 | - The synthesizer C module (sfx_gen.c/.h). 8 | - A Qt based GUI program for creating sound parameters & Wave files. 9 | - A CLI program to create Wave files from parameters. 10 | 11 | Sfx_gen extends the original sfxr code by adding two new wave types: Triangle 12 | & Pink Noise. 13 | 14 | 15 | Synthesizer Module 16 | ------------------ 17 | 18 | To use the C library in your program include the sfx_gen.\* files in 19 | your project and implement the `sfx_random()` function somewhere in your code. 20 | 21 | Here is a minimal example: 22 | 23 | #include 24 | #include "sfx_gen.h" 25 | 26 | int sfx_random(int range) { 27 | return rand() % range; 28 | } 29 | 30 | ... 31 | 32 | SfxParams param; 33 | SfxSynth* synth = sfx_allocSynth(SFX_I16, 44100, 10); 34 | 35 | srand(999); 36 | sfx_genRandomize(¶m, SFX_SQUARE); 37 | int sampleCount = sfx_generateWave(synth, ¶m); 38 | 39 | // Use the generated samples as desired, e.g. 40 | alBufferData(bufId, AL_FORMAT_MONO16, synth->samples.i16, 41 | sampleCount * sizeof(int16_t), synth->sampleRate); 42 | 43 | free(synth); 44 | 45 | Sound parameters can be saved as rFX files (compatible with [rFXGen] v2.5) and 46 | reloaded later: 47 | 48 | const char* error = sfx_saveRfx(¶m, "test_sound.rfx"); 49 | if (error) 50 | printf("Save RFX: %s\n", error); 51 | 52 | ... 53 | 54 | error = sfx_loadParams(¶m, "test_sound.rfx", NULL); 55 | 56 | The compiled code can be modified by defining the following macros: 57 | 58 | Macro Name | Effect 59 | -------------------------|-------------- 60 | CONFIG_SFX_NO_FILEIO | Exclude file load/save functions. 61 | CONFIG_SFX_NO_GENERATORS | Exclude parameter generator functions. 62 | SINGLE_FORMAT=[1,3] | Hardcode sfx_generateWave output sample format. 63 | 64 | 65 | GUI Program 66 | ----------- 67 | 68 | The `qfxgen` program... 69 | 70 | ![Screenshot](https://github.com/WickedSmoke/sfx_gen/wiki/images/qfxgen.jpg) 71 | 72 | ### Building the GUI 73 | 74 | Qt 5 and OpenAL are required. Project files are provided for QMake & [Copr]. 75 | 76 | To build with QMake: 77 | 78 | qmake-qt5; make 79 | 80 | To build both qfxgen & sfxgen with copr: 81 | 82 | copr 83 | 84 | 85 | CLI Program 86 | ----------- 87 | 88 | The `sfxgen` program can generate 44.1KHz Wave files from multiple `.rfx` 89 | files in two ways. 90 | 91 | 1. The output filename for each Wave can be specified by using the `-o` 92 | option after each input file. 93 | 2. If `-o` does not follow the input filename then that path with the 94 | extension replaced with `.wav` is used as the output filename. 95 | 96 | To generate Wave files for an entire directory shell wildcards can be used: 97 | 98 | sfxgen my_sounds/*.rfx 99 | 100 | ### Building the CLI 101 | 102 | To build on Unix systems: 103 | 104 | cc main.c -Isupport -lm -o sfxgen 105 | 106 | 107 | [sfxr]: http://www.drpetter.se/project_sfxr.html 108 | [rFXGen]: https://raylibtech.itch.io/rfxgen 109 | [Copr]: http://urlan.sourceforge.net/copr.html 110 | -------------------------------------------------------------------------------- /sfx_gen.h: -------------------------------------------------------------------------------- 1 | /* 2 | sfx_gen - Sound Effect Generator 3 | A stand-alone C version of DrPetter's sfxr synthesizer from 4 | http://www.drpetter.se/project_sfxr.html 5 | 6 | Copyright (c) 2022,2023 Karl Robillard 7 | 8 | This code may be used under the terms of the MIT license (see sfx_gen.c). 9 | */ 10 | 11 | #include 12 | 13 | #define SFX_VERSION_STR "0.6.0" 14 | #define SFX_VERSION 0x000600 15 | 16 | enum SfxWaveType { 17 | SFX_SQUARE, 18 | SFX_SAWTOOTH, 19 | SFX_SINE, 20 | SFX_NOISE, 21 | SFX_TRIANGLE, 22 | SFX_PINK_NOISE 23 | }; 24 | 25 | // Sound parameters (96 bytes matching rFXGen WaveParams) 26 | typedef struct SfxParams 27 | { 28 | // Random seed used to generate the wave 29 | uint32_t randSeed; 30 | 31 | // Wave type (square, sawtooth, sine, noise) 32 | int waveType; 33 | 34 | // Wave envelope parameters 35 | float attackTime; 36 | float sustainTime; 37 | float sustainPunch; 38 | float decayTime; 39 | 40 | // Frequency parameters 41 | float startFrequency; 42 | float minFrequency; 43 | float slide; 44 | float deltaSlide; 45 | float vibratoDepth; 46 | float vibratoSpeed; 47 | //float vibratoPhaseDelay; // Unused in sfxr code. 48 | 49 | // Tone change parameters 50 | float changeAmount; 51 | float changeSpeed; 52 | 53 | // Square wave parameters 54 | float squareDuty; 55 | float dutySweep; 56 | 57 | // Repeat parameters 58 | float repeatSpeed; 59 | 60 | // Phaser parameters 61 | float phaserOffset; 62 | float phaserSweep; 63 | 64 | // Filter parameters 65 | float lpfCutoff; 66 | float lpfCutoffSweep; 67 | float lpfResonance; 68 | float hpfCutoff; 69 | float hpfCutoffSweep; 70 | } 71 | SfxParams; 72 | 73 | // There are 8 parameters with -1,1 range: 74 | // slide, deltaSlide, changeAmount, dutySweep, 75 | // phaserOffset, phaserSweep, lpfCutoffSweep, hpfCutoffSweep 76 | #define SFX_NEGATIVE_ONE_MASK 0x0025A4C0 77 | 78 | enum SfxSampleFormat { 79 | SFX_U8, // uint8_t 80 | SFX_I16, // int16_t 81 | SFX_F32 // float 82 | }; 83 | 84 | typedef struct SfxSynth { 85 | int sampleFormat; 86 | int sampleRate; // Must be 44100 for now 87 | int maxDuration; // Length in seconds 88 | union { 89 | uint8_t* u8; 90 | int16_t* i16; 91 | float* f; 92 | } samples; // sampleRate * maxDuration 93 | float noiseBuffer[32]; // Random values for SFX_NOISE/SFX_PINK_NOISE 94 | float pinkWhiteValue[5]; // SFX_PINK_NOISE 95 | float phaserBuffer[1024]; 96 | } 97 | SfxSynth; 98 | 99 | #ifdef __cplusplus 100 | extern "C" { 101 | #endif 102 | 103 | void sfx_resetParams(SfxParams *params); 104 | SfxSynth* sfx_allocSynth(int format, int sampleRate, int maxDuration); 105 | int sfx_generateWave(SfxSynth*, const SfxParams* params); 106 | 107 | // Load/Save functions 108 | const char* sfx_loadParams(SfxParams *params, const char *fileName, 109 | float* sfsVolume); 110 | const char* sfx_saveRfx(const SfxParams *params, const char *fileName); 111 | 112 | // Parameter generator functions 113 | void sfx_genPickupCoin(SfxParams*); 114 | void sfx_genLaserShoot(SfxParams*); 115 | void sfx_genExplosion(SfxParams*); 116 | void sfx_genPowerup(SfxParams*); 117 | void sfx_genHitHurt(SfxParams*); 118 | void sfx_genJump(SfxParams*); 119 | void sfx_genBlipSelect(SfxParams*); 120 | void sfx_genSynth(SfxParams*); 121 | void sfx_genRandomize(SfxParams*, int waveType); 122 | void sfx_mutate(SfxParams *params, float range, uint32_t mask); 123 | 124 | #ifdef __cplusplus 125 | } 126 | #endif 127 | -------------------------------------------------------------------------------- /support/audio_openal.c: -------------------------------------------------------------------------------- 1 | /* 2 | Audio Module - OpenAL Backend 3 | Copyright 2005-2012,2022 Karl Robillard 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a 6 | copy of this software and associated documentation files (the "Software"), 7 | to deal in the Software without restriction, including without limitation 8 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | and/or sell copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 18 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 | DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | 25 | #include 26 | #include 27 | 28 | #ifdef __APPLE__ 29 | #include 30 | #include 31 | #else 32 | #include 33 | #include 34 | #define AL_ALEXT_PROTOTYPES 35 | #include 36 | #endif 37 | 38 | #include "audio.h" 39 | 40 | 41 | #define FX_COUNT 4 42 | #define AMBIENT_COUNT 0 43 | #define SOURCE_COUNT FX_COUNT + AMBIENT_COUNT 44 | 45 | enum AudioState 46 | { 47 | AUDIO_DOWN, 48 | AUDIO_AL_UP, 49 | AUDIO_THREAD_UP 50 | }; 51 | 52 | static int _audioUp = AUDIO_DOWN; 53 | static ALCdevice* _adevice = 0; 54 | static ALCcontext* _acontext = 0; 55 | static ALuint _asource[ SOURCE_COUNT ]; 56 | 57 | 58 | /** 59 | Called once at program startup. 60 | Returns 0 on a fatal error. 61 | */ 62 | int aud_startup() 63 | { 64 | _adevice = alcOpenDevice(0); 65 | if (! _adevice) 66 | return 0; 67 | _acontext = alcCreateContext(_adevice, 0); 68 | alcMakeContextCurrent(_acontext); 69 | 70 | alGenSources(SOURCE_COUNT, _asource); 71 | 72 | _audioUp = AUDIO_AL_UP; 73 | return 1; 74 | } 75 | 76 | 77 | /** 78 | Called once when program exits. 79 | It is safe to call this even if aud_startup() was not called. 80 | */ 81 | void aud_shutdown() 82 | { 83 | if (_audioUp) { 84 | alDeleteSources(SOURCE_COUNT, _asource); 85 | alcDestroyContext(_acontext); 86 | alcCloseDevice(_adevice); 87 | 88 | _audioUp = AUDIO_DOWN; 89 | } 90 | } 91 | 92 | 93 | void aud_stopAll() 94 | { 95 | if (_audioUp) { 96 | ALuint i; 97 | for (i = 0; i < SOURCE_COUNT; ++i) { 98 | alSourceStop(_asource[i]); 99 | alSourcei(_asource[i], AL_BUFFER, 0); 100 | } 101 | } 102 | } 103 | 104 | 105 | /** 106 | Call to stop (or later resume) processing audio. 107 | */ 108 | void aud_pauseProcessing( int paused ) 109 | { 110 | #ifdef ALC_SOFT_pause_device 111 | if (_audioUp) { 112 | if (paused) 113 | alcDevicePauseSOFT(_adevice); 114 | else 115 | alcDeviceResumeSOFT(_adevice); 116 | } 117 | #endif 118 | } 119 | 120 | 121 | int aud_loadBufferI16(uint32_t bufId, const int16_t* samples, int sampleCount, 122 | int stereo, int freq) 123 | { 124 | if (_audioUp) { 125 | ALenum err; 126 | alBufferData(bufId, stereo ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16, 127 | samples, sampleCount * sizeof(int16_t), freq); 128 | if ((err = alGetError()) != AL_NO_ERROR) { 129 | fprintf(stderr, "ERROR: alBufferData %d", err); 130 | return 0; 131 | } 132 | } 133 | return 1; 134 | } 135 | 136 | 137 | int aud_loadBufferF32(uint32_t bufId, const float* samples, int sampleCount, 138 | int stereo, int freq) 139 | { 140 | if (_audioUp) { 141 | const float* send = samples + sampleCount; 142 | int16_t* pcm; 143 | int16_t* it; 144 | ALenum err; 145 | ALenum format = stereo ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16; 146 | // AL_FORMAT_STEREO8 : AL_FORMAT_MONO8; 147 | ALsizei bytes = sampleCount * sizeof(int16_t); 148 | 149 | it = pcm = (int16_t*) malloc(bytes); 150 | if (pcm) { 151 | for (; samples != send; ++samples) 152 | *it++ = (int16_t) (samples[0] * 32767.0f); // -32768 to 32767. 153 | 154 | alBufferData(bufId, format, pcm, bytes, freq); 155 | free(pcm); 156 | } 157 | 158 | if ((err = alGetError()) != AL_NO_ERROR) { 159 | fprintf(stderr, "ERROR: alBufferData %d", err); 160 | return 0; 161 | } 162 | } 163 | return 1; 164 | } 165 | 166 | 167 | void aud_genBuffers(int count, uint32_t* ids) 168 | { 169 | if (_audioUp) { 170 | alGenBuffers(count, ids); 171 | alGetError(); 172 | } 173 | } 174 | 175 | 176 | void aud_freeBuffers(int count, uint32_t* ids) 177 | { 178 | if (_audioUp) 179 | alDeleteBuffers(count, ids); 180 | } 181 | 182 | 183 | /* 184 | \return source Id. 185 | */ 186 | uint32_t aud_playSound(uint32_t bufferId) 187 | { 188 | static int sn = 0; 189 | if (bufferId && _audioUp) { 190 | ALuint src = _asource[ sn ]; 191 | ++sn; 192 | if (sn == FX_COUNT) 193 | sn = 0; 194 | 195 | alSourcei(src, AL_BUFFER, bufferId); 196 | alSourcePlay(src); 197 | return src; 198 | } 199 | return 0; 200 | } 201 | 202 | 203 | void aud_stopSound(uint32_t sourceId) 204 | { 205 | if (_audioUp) { 206 | alSourceStop(sourceId); 207 | alSourcei(sourceId, AL_BUFFER, 0); 208 | } 209 | } 210 | 211 | 212 | /* 213 | \param vol 0.0 to 1.0 214 | */ 215 | void aud_setSoundVolume(float vol) 216 | { 217 | if (_audioUp) 218 | alListenerf(AL_GAIN, vol); 219 | } 220 | 221 | 222 | //EOF 223 | -------------------------------------------------------------------------------- /gui_qt/SfxWindow.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | sfx_gen Qt GUI 3 | 4 | Copyright 2022 Karl Robillard 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include "SfxWindow.h" 40 | #include "version.h" 41 | #ifdef USE_FAUN 42 | #include 43 | #else 44 | #include "audio.h" 45 | #endif 46 | #include "sfx_gen.h" 47 | #include "saveWave.h" 48 | 49 | #include "FilesModel.cpp" 50 | 51 | #if 1 52 | #include "well512.h" 53 | static Well512 rng; 54 | #define SEED_RNG(S) well512_init(&rng, S) 55 | #else 56 | static QRandomGenerator rng; 57 | #define SEED_RNG(S) rng.seed(S) 58 | #endif 59 | 60 | // Random function required by sfx_gen. 61 | extern "C" int sfx_random(int range) 62 | { 63 | #ifdef WELL512_H 64 | return well512_genU32(&rng) % range; 65 | #else 66 | return rng.bounded(range); 67 | #endif 68 | } 69 | 70 | // Audio wave data 71 | struct Wave 72 | { 73 | uint32_t frameCount; // Total number of frames (considering channels) 74 | uint16_t sampleRate; // Frequency (samples per second) 75 | uint16_t sampleSize; // Bit depth (bits per sample): 8, 16, 32 76 | uint16_t channels; // Number of channels (1-mono, 2-stereo, ...) 77 | float* data; // Buffer data pointer 78 | }; 79 | 80 | #define MAX_WAVE_SLOTS 4 81 | struct WaveTables { 82 | #ifndef USE_FAUN 83 | uint32_t bufId[MAX_WAVE_SLOTS]; 84 | int srcId[MAX_WAVE_SLOTS]; 85 | #endif 86 | Wave wave[MAX_WAVE_SLOTS]; 87 | SfxParams params[MAX_WAVE_SLOTS]; 88 | SfxParams clip; 89 | }; 90 | 91 | #define GEN_COUNT 10 92 | static const char* genName[] = { 93 | "Pickup/Coin", 94 | "Laser/Shoot", 95 | "Explosion", 96 | "PowerUp", 97 | "Hit/Hurt", 98 | "Jump", 99 | "Blip/Select", 100 | "Synth", 101 | "Mutate", 102 | "Randomize" 103 | }; 104 | 105 | #define WFORM_COUNT 6 106 | static const char* wformName[] = { 107 | "Square", 108 | "Sawtooth", 109 | "Sinewave", 110 | "Noise", 111 | "Triangle", 112 | "Pink Noise" 113 | }; 114 | 115 | static const char* wformIcon[] = { 116 | ":/icons/wave-square.svg", 117 | ":/icons/wave-sawtooth.svg", 118 | ":/icons/wave-sine.svg", 119 | ":/icons/wave-noise.svg", 120 | ":/icons/wave-triangle.svg", 121 | ":/icons/wave-pinknoise.svg" 122 | }; 123 | 124 | static void addGeneratorButton(QWidget* parent, int id, QBoxLayout* lo) 125 | { 126 | QPushButton* btn = new QPushButton(genName[id]); 127 | btn->setProperty("gid", id); 128 | parent->connect(btn, SIGNAL(clicked(bool)), SLOT(generateSound())); 129 | lo->addWidget(btn); 130 | } 131 | 132 | void SfxWindow::layoutGenerators(QBoxLayout* plo) 133 | { 134 | QBoxLayout* lo = new QVBoxLayout; 135 | int i; 136 | 137 | lo->setSpacing(4); 138 | 139 | for (i = 0; i < GEN_COUNT-2; ++i) 140 | addGeneratorButton(this, i, lo); 141 | lo->addSpacing(8); 142 | 143 | QButtonGroup* grp = new QButtonGroup(this); 144 | QPushButton* btn; 145 | for (i = 0; i < WFORM_COUNT; ++i) { 146 | btn = _waveType[i] = new QPushButton(wformName[i]); 147 | btn->setIcon(QIcon(wformIcon[i])); 148 | btn->setCheckable(true); 149 | btn->setFlat(true); 150 | grp->addButton(btn, i); 151 | lo->addWidget(btn); 152 | } 153 | grp->button(0)->setChecked(true); 154 | connect(grp, SIGNAL(idToggled(int,bool)), SLOT(chooseWaveForm(int,bool))); 155 | lo->addSpacing(8); 156 | 157 | for (i = GEN_COUNT-2; i < GEN_COUNT; ++i) 158 | addGeneratorButton(this, i, lo); 159 | 160 | lo->addStretch(1); 161 | 162 | plo->addLayout(lo); 163 | } 164 | 165 | void SfxWindow::addSlider(QGridLayout* grid, int row, const char* label, 166 | int neg) 167 | { 168 | QSlider* slid = _param[row] = new QSlider(Qt::Horizontal); 169 | slid->setProperty("pid", row); 170 | slid->setTracking(false); 171 | 172 | const char* ltext; 173 | if (row == PARAM_VOL) { 174 | slid->setRange(0, 100); 175 | slid->setPageStep(10); 176 | connect(slid, SIGNAL(valueChanged(int)), SLOT(volumeChanged(int))); 177 | ltext = "0.00"; 178 | } else { 179 | slid->setRange(neg ? -400 : 0, 400); 180 | slid->setPageStep(40); 181 | connect(slid, SIGNAL(valueChanged(int)), SLOT(paramChanged(int))); 182 | ltext = "0.000"; 183 | } 184 | 185 | QLabel* vlabel = _paramReadout[row] = new QLabel(ltext); 186 | grid->addWidget(new QLabel(label), row, 1, Qt::AlignLeft); 187 | grid->addWidget(slid, row, 2); 188 | grid->addWidget(vlabel, row, 3, Qt::AlignRight); 189 | } 190 | 191 | static const char* paramName[PARAM_COUNT] = { 192 | "Volume", 193 | "Attack time", // ENVELOPE 194 | "Sustain time", 195 | "Sustain punch", 196 | "Decay time", 197 | "Start", // FREQUENCY 198 | "Minimum", 199 | "Amount", // SLIDE 200 | "Delta", 201 | "Depth", // VIBRATO 202 | "Speed", 203 | "Change", // TONE 204 | "Change speed", 205 | "Cycle", // DUTY 206 | "Sweep", 207 | "Speed", // REPEAT 208 | "Offset", // PHASER 209 | "Sweep", 210 | "Cutoff", // LPF 211 | "Cutoff sweep", 212 | "Resonance", 213 | "Cutoff", // HPF 214 | "Cutoff sweep" 215 | }; 216 | 217 | static uint32_t paramNegative = SFX_NEGATIVE_ONE_MASK << 1; 218 | 219 | void SfxWindow::layoutParams(QBoxLayout* plo) 220 | { 221 | QGridLayout* grid = new QGridLayout; 222 | grid->setSpacing(4); 223 | 224 | grid->addWidget(new QLabel("ENVELOPE"), 1, 0, Qt::AlignRight); 225 | grid->addWidget(new QLabel("FREQUENCY"), 5, 0, Qt::AlignRight); 226 | grid->addWidget(new QLabel("SLIDE"), 7, 0, Qt::AlignRight); 227 | grid->addWidget(new QLabel("VIBRATO"), 9, 0, Qt::AlignRight); 228 | grid->addWidget(new QLabel("TONE"), 11, 0, Qt::AlignRight); 229 | grid->addWidget(new QLabel("DUTY"), 13, 0, Qt::AlignRight); 230 | grid->addWidget(new QLabel("REPEAT"), 15, 0, Qt::AlignRight); 231 | grid->addWidget(new QLabel("PHASER"), 16, 0, Qt::AlignRight); 232 | grid->addWidget(new QLabel("LPF"), 18, 0, Qt::AlignRight); 233 | grid->addWidget(new QLabel("HPF"), 21, 0, Qt::AlignRight); 234 | 235 | for (int i = 0; i < PARAM_COUNT; ++i) 236 | addSlider(grid, i, paramName[i], paramNegative & (1 << i)); 237 | 238 | QWidget* label = grid->itemAtPosition(1, 0)->widget(); 239 | int lwidth = label->fontMetrics().horizontalAdvance("-0.333"); 240 | grid->setColumnMinimumWidth(3, lwidth); 241 | grid->setRowStretch(PARAM_COUNT, 1); 242 | 243 | plo->addLayout(grid, 2); 244 | } 245 | 246 | SfxWindow::SfxWindow() 247 | { 248 | int i, vol; 249 | 250 | setWindowTitle(APP_NAME); 251 | 252 | _paramAssign = true; 253 | _playOnChange = true; 254 | _activeWav = 0; 255 | _recentPid = 0; 256 | 257 | _synth = sfx_allocSynth(SFX_F32, 44100, 10); 258 | 259 | _wav = new WaveTables; 260 | for (i = 0; i < MAX_WAVE_SLOTS; ++i) { 261 | #ifndef USE_FAUN 262 | _wav->bufId[i] = 0; 263 | _wav->srcId[i] = 0; 264 | #endif 265 | memset(_wav->wave + i, 0, sizeof(Wave)); 266 | sfx_resetParams(_wav->params + i); 267 | } 268 | _wav->clip.waveType = -1; 269 | 270 | createActions(); 271 | createMenus(); 272 | createTools(); 273 | 274 | QWidget* mainWid = new QWidget; 275 | QBoxLayout* lo = new QVBoxLayout(mainWid); 276 | QBoxLayout* loH = new QHBoxLayout; 277 | lo->addLayout(loH); 278 | layoutGenerators(loH); 279 | layoutParams(loH); 280 | 281 | _files = new FilesModel(this); 282 | QListView* flist = new QListView; 283 | flist->setUniformItemSizes(true); 284 | flist->setModel(_files); 285 | connect(flist, SIGNAL(activated(const QModelIndex&)), 286 | SLOT(chooseFile(const QModelIndex&))); 287 | loH->addWidget(flist); 288 | 289 | _wavePix = QPixmap(640, 58); 290 | _wavePic = new QLabel; 291 | _wavePic->setPixmap(_wavePix); 292 | lo->addWidget(_wavePic, 0, Qt::AlignHCenter); 293 | 294 | loH = new QHBoxLayout; 295 | lo->addLayout(loH); 296 | for (i = 0; i < 3; ++i) { 297 | _stats[i] = new QLabel; 298 | loH->addWidget(_stats[i], 0, Qt::AlignHCenter); 299 | } 300 | 301 | setCentralWidget(mainWid); 302 | 303 | { 304 | QSettings settings; 305 | resize(settings.value("window-size", QSize(700, 480)).toSize()); 306 | _prevProjPath = settings.value("prev-project").toString(); 307 | vol = settings.value("volume", 100).toInt(); 308 | _actPoc->setChecked(settings.value("play-on-change", true).toBool()); 309 | } 310 | 311 | #ifdef USE_FAUN 312 | const char* error = 313 | faun_startup(MAX_WAVE_SLOTS, MAX_WAVE_SLOTS, 0, 0, APP_NAME); 314 | if (error) { 315 | QString msg("faun_startup: "); 316 | msg.append(error); 317 | QMessageBox::critical(this, "Audio System", msg); 318 | } 319 | #else 320 | if (aud_startup()) { 321 | aud_genBuffers(MAX_WAVE_SLOTS, _wav->bufId); 322 | } else { 323 | QMessageBox::critical(this, "Audio System", "aud_startup() failed!\n"); 324 | } 325 | #endif 326 | 327 | _param[0]->setValue(vol); 328 | updateParameterWidgets(_wav->params); 329 | 330 | SEED_RNG( QRandomGenerator::global()->generate() ); 331 | } 332 | 333 | 334 | SfxWindow::~SfxWindow() 335 | { 336 | #ifdef USE_FAUN 337 | faun_shutdown(); 338 | #else 339 | aud_stopAll(); 340 | aud_freeBuffers(MAX_WAVE_SLOTS, _wav->bufId); 341 | aud_shutdown(); 342 | #endif 343 | 344 | free(_synth); 345 | 346 | for (int i = 0; i < MAX_WAVE_SLOTS; ++i) 347 | free(_wav->wave[i].data); 348 | delete _wav; 349 | } 350 | 351 | void SfxWindow::closeEvent( QCloseEvent* ev ) 352 | { 353 | QSettings settings; 354 | settings.setValue("window-size", size()); 355 | settings.setValue("prev-project", _prevProjPath); 356 | settings.setValue("volume", _param[0]->value()); 357 | settings.setValue("play-on-change", _playOnChange); 358 | 359 | QMainWindow::closeEvent( ev ); 360 | } 361 | 362 | void SfxWindow::showAbout() 363 | { 364 | QMessageBox::information( this, "About " APP_NAME, 365 | "Version " APP_VERSION "\n\nCopyright (c) 2022,2023 Karl Robillard" ); 366 | } 367 | 368 | 369 | void SfxWindow::createActions() 370 | { 371 | #define STD_ICON(id) style()->standardIcon(QStyle::id) 372 | 373 | _actOpen = new QAction(STD_ICON(SP_DialogOpenButton), "&Open...", this ); 374 | _actOpen->setShortcuts(QKeySequence::Open); 375 | connect(_actOpen, SIGNAL(triggered()), SLOT(open())); 376 | 377 | _actSave = new QAction(STD_ICON(SP_DialogSaveButton), "&Save", this ); 378 | _actSave->setShortcuts(QKeySequence::Save); 379 | _actSave->setEnabled(false); 380 | connect(_actSave, SIGNAL(triggered()), SLOT(save())); 381 | 382 | _actSaveAs = new QAction("Save &As", this); 383 | _actSaveAs->setShortcuts(QKeySequence::SaveAs); 384 | connect(_actSaveAs, SIGNAL(triggered()), SLOT(saveAs())); 385 | 386 | _actPlay = new QAction(STD_ICON(SP_MediaPlay), "&Play Sound", this ); 387 | _actPlay->setShortcut(Qt::Key_Space); 388 | connect(_actPlay, SIGNAL(triggered()), SLOT(playSound())); 389 | 390 | _actPoc = new QAction(STD_ICON(SP_MediaVolume), "Play on change", this); 391 | _actPoc->setCheckable(true); 392 | connect(_actPoc, SIGNAL(toggled(bool)), SLOT(setPoc(bool))); 393 | } 394 | 395 | 396 | #if QT_VERSION >= 0x060400 397 | #define ADD_ACTION(L, M, K) menu->addAction(L, K, this, M) 398 | #else 399 | #define ADD_ACTION(L, M, K) menu->addAction(L, this, M, K) 400 | #endif 401 | 402 | void SfxWindow::createMenus() 403 | { 404 | QMenu* menu; 405 | QMenuBar* bar = menuBar(); 406 | 407 | menu = bar->addMenu( "&File" ); 408 | menu->addAction(_actOpen); 409 | menu->addAction(_actSave); 410 | menu->addAction(_actSaveAs); 411 | menu->addSeparator(); 412 | ADD_ACTION("&Quit", SLOT(close()), QKeySequence::Quit); 413 | 414 | menu = bar->addMenu( "&Edit" ); 415 | ADD_ACTION("&Copy", SLOT(copy()), QKeySequence::Copy); 416 | _actPaste = 417 | ADD_ACTION("&Paste", SLOT(paste()), QKeySequence::Paste); 418 | menu->addSeparator(); 419 | ADD_ACTION("Reset Parameter", SLOT(resetParam()), 420 | QKeySequence(Qt::Key_Backspace)); 421 | ADD_ACTION("&Mutate", SLOT(mutate()), QKeySequence(Qt::Key_F3)); 422 | ADD_ACTION("&Randomize", SLOT(randomize()), QKeySequence(Qt::Key_F4)); 423 | 424 | bar->addSeparator(); 425 | 426 | menu = bar->addMenu( "&Help" ); 427 | menu->addAction("&About", this, SLOT(showAbout())); 428 | 429 | _actPaste->setEnabled(false); 430 | } 431 | 432 | 433 | void SfxWindow::createTools() 434 | { 435 | QAction* act; 436 | QToolButton* btn; 437 | 438 | _tools = addToolBar(""); 439 | _tools->addAction(_actOpen); 440 | _tools->addAction(_actSave); 441 | _tools->addSeparator(); 442 | _tools->addAction(_actPlay); 443 | _tools->addAction(_actPoc); 444 | 445 | btn = qobject_cast(_tools->widgetForAction(_actPlay)); 446 | if (btn) 447 | btn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); 448 | 449 | _tools->addWidget(new QLabel(" Slot:")); 450 | 451 | QButtonGroup* grp = new QButtonGroup(this); 452 | for (int i = 0; i < MAX_WAVE_SLOTS; ++i) { 453 | btn = new QToolButton; 454 | btn->setText(QString::number(i+1)); 455 | btn->setCheckable(true); 456 | grp->addButton(btn, i); 457 | _tools->addWidget(btn); 458 | 459 | act = new QAction(this); 460 | act->setShortcut(QKeySequence(Qt::CTRL | (Qt::Key_1 + i))); 461 | connect(act, SIGNAL(triggered(bool)), btn, SLOT(click())); 462 | addAction(act); 463 | } 464 | grp->button(0)->setChecked(true); 465 | connect(grp, SIGNAL(idToggled(int,bool)), SLOT(chooseWaveSlot(int,bool))); 466 | } 467 | 468 | 469 | void SfxWindow::setProjectFile( const QString& file ) 470 | { 471 | _prevProjPath = file; 472 | setWindowTitle(file + " - " APP_NAME); 473 | _actSave->setEnabled(true); 474 | } 475 | 476 | 477 | #define UTF8(str) str.toUtf8().constData() 478 | 479 | bool SfxWindow::open(const QString& file, bool updateList) 480 | { 481 | SfxParams* sp = _wav->params + _activeWav; 482 | const char* err = sfx_loadParams(sp, UTF8(file), NULL); 483 | if (err) { 484 | QMessageBox::warning(this, "Load Error", file + ":\n" + err); 485 | return false; 486 | } else { 487 | setProjectFile(file); 488 | updateParameterWidgets(sp); 489 | regenerate(false); 490 | 491 | if (updateList) { 492 | QFileInfo info(file); 493 | _files->setDirectory(info.absolutePath(), "*.rfx"); 494 | } 495 | return true; 496 | } 497 | } 498 | 499 | 500 | void SfxWindow::open() 501 | { 502 | QString fn; 503 | QString path(_prevProjPath); 504 | 505 | fn = QFileDialog::getOpenFileName(this, "Open Parameters", path, 506 | "Parameters (*.rfx *.sfs)"); 507 | if (! fn.isEmpty()) 508 | open(fn, true); 509 | } 510 | 511 | 512 | bool SfxWindow::saveRfx(const QString& file) 513 | { 514 | const char* err = sfx_saveRfx(_wav->params + _activeWav, UTF8(file)); 515 | if (err) { 516 | QMessageBox::warning(this, "RFX Save Error", file + ":\n" + err); 517 | return false; 518 | } 519 | return true; 520 | } 521 | 522 | 523 | void SfxWindow::save() 524 | { 525 | if (_actSave->isEnabled() && ! _prevProjPath.isEmpty()) 526 | saveRfx(_prevProjPath); 527 | } 528 | 529 | 530 | bool SfxWindow::saveWaveFile(const Wave* wav, const QString& file) 531 | { 532 | int16_t* pcm; 533 | int16_t* it; 534 | const char* err; 535 | uint32_t bytes = wav->frameCount * sizeof(int16_t); 536 | 537 | pcm = (int16_t*) malloc(bytes); 538 | if (pcm) { 539 | const float* samples = (const float*) wav->data; 540 | const float* send = samples + wav->frameCount; 541 | for (it = pcm; samples != send; ++samples) 542 | *it++ = (int16_t) (samples[0] * 32767.0f); // -32768 to 32767. 543 | 544 | err = saveWave(pcm, bytes, wav->sampleRate, 16, wav->channels, 545 | UTF8(file)); 546 | free(pcm); 547 | 548 | if (err) { 549 | QMessageBox::warning(this, "WAVE Save Error", file + ":\n" + err); 550 | return false; 551 | } 552 | return true; 553 | } 554 | return false; 555 | } 556 | 557 | 558 | void SfxWindow::saveAs() 559 | { 560 | QString fn; 561 | QString path(_prevProjPath); 562 | 563 | fn = QFileDialog::getSaveFileName(this, "Save Sound As", path, 564 | "Parameters (*.rfx);;Wave (*.wav)"); 565 | if (! fn.isEmpty()) { 566 | if (fn.endsWith(".wav", Qt::CaseInsensitive)) { 567 | saveWaveFile(_wav->wave + _activeWav, fn); 568 | } else { 569 | if (saveRfx(fn)) { 570 | setProjectFile(fn); 571 | 572 | QFileInfo info(fn); 573 | _files->setDirectory(info.absolutePath(), "*.rfx"); 574 | } 575 | } 576 | } 577 | } 578 | 579 | 580 | void SfxWindow::copy() 581 | { 582 | _wav->clip = _wav->params[_activeWav]; 583 | _actPaste->setEnabled(true); 584 | } 585 | 586 | 587 | void SfxWindow::paste() 588 | { 589 | if (_wav->clip.waveType >= 0) { 590 | SfxParams* sp = _wav->params + _activeWav; 591 | *sp = _wav->clip; 592 | updateParameterWidgets(sp); 593 | regenerate(false); 594 | } 595 | } 596 | 597 | 598 | void SfxWindow::setPoc(bool on) 599 | { 600 | _playOnChange = on; 601 | } 602 | 603 | 604 | void SfxWindow::playSound() 605 | { 606 | #ifdef USE_FAUN 607 | faun_playSource(_activeWav, _activeWav, FAUN_PLAY_ONCE); 608 | #else 609 | int i = _activeWav; 610 | _wav->srcId[i] = aud_playSound(_wav->bufId[i]); 611 | #endif 612 | } 613 | 614 | 615 | void SfxWindow::generateSound() 616 | { 617 | int gid = sender()->property("gid").toInt(); 618 | if (gid < 0) 619 | return; 620 | 621 | if (gid < GEN_COUNT-2) { 622 | SfxParams* sp = _wav->params + _activeWav; 623 | 624 | uint32_t seed = QRandomGenerator::global()->generate(); 625 | SEED_RNG(seed); 626 | switch (gid) { 627 | case 0: sfx_genPickupCoin(sp); break; 628 | case 1: sfx_genLaserShoot(sp); break; 629 | case 2: sfx_genExplosion(sp); break; 630 | case 3: sfx_genPowerup(sp); break; 631 | case 4: sfx_genHitHurt(sp); break; 632 | case 5: sfx_genJump(sp); break; 633 | case 6: sfx_genBlipSelect(sp); break; 634 | case 7: sfx_genSynth(sp); break; 635 | } 636 | sp->randSeed = seed; 637 | 638 | updateParameterWidgets(sp); 639 | regenerate(true); 640 | } 641 | else if (gid == 8) 642 | mutate(); 643 | else if (gid == 9) 644 | randomize(); 645 | } 646 | 647 | 648 | void SfxWindow::mutate() 649 | { 650 | SfxParams* sp = _wav->params + _activeWav; 651 | sfx_mutate(sp, 0.1f, 0xffffdf); 652 | updateParameterWidgets(sp); 653 | regenerate(true); 654 | } 655 | 656 | 657 | void SfxWindow::randomize() 658 | { 659 | SfxParams* sp = _wav->params + _activeWav; 660 | 661 | uint32_t seed = QRandomGenerator::global()->generate(); 662 | SEED_RNG(seed); 663 | sfx_genRandomize(sp, sfx_random(4)); 664 | sp->randSeed = seed; 665 | 666 | updateParameterWidgets(sp); 667 | regenerate(true); 668 | } 669 | 670 | 671 | void SfxWindow::resetParam() 672 | { 673 | if (_recentPid > 0) { 674 | SfxParams sp; 675 | sfx_resetParams(&sp); 676 | const float* fval = &sp.attackTime; 677 | _param[ _recentPid ]->setValue(fval[ _recentPid-1 ] * 400.0f); 678 | } 679 | } 680 | 681 | 682 | void SfxWindow::updateParameterWidgets(const SfxParams* sp) 683 | { 684 | const float* fval = &sp->attackTime; 685 | _paramAssign = false; 686 | _waveType[ sp->waveType ]->setChecked(true); 687 | for (int p = 1; p < PARAM_COUNT; ++p) { 688 | _param[p]->setValue(fval[0] * 400.0f); 689 | ++fval; 690 | } 691 | _paramAssign = true; 692 | } 693 | 694 | 695 | static void drawWave(QPixmap* pix, const Wave* wave) 696 | { 697 | int width = pix->width(); 698 | int halfH = pix->height() / 2; 699 | int sy0, sy1, sy2; 700 | float samplePos = 0.0f; 701 | float sampleInc = float(wave->frameCount * wave->channels) / float(width); 702 | float sampleLast = float(wave->frameCount - 1); 703 | float oneThirdInc = sampleInc / 3.0f; 704 | float twoThirdInc = 2.0f * oneThirdInc; 705 | float sampleScale = float(halfH); 706 | const float* wdata = (const float*) wave->data; 707 | 708 | pix->fill(QColor(0,0x22,0x2b)); 709 | 710 | QPainter p(pix); 711 | p.setPen(QColor(255,165,60,100)); 712 | 713 | #define SAMPLE_Y(pos) halfH + int(wdata[int(pos)] * sampleScale) 714 | 715 | sy0 = SAMPLE_Y(0); 716 | 717 | for (int x = 0; x < width; x++) { 718 | sy1 = SAMPLE_Y(samplePos + oneThirdInc); 719 | sy2 = SAMPLE_Y(samplePos + twoThirdInc); 720 | 721 | p.drawLine(x, sy0, x, sy1); 722 | p.drawLine(x, sy1, x, sy2); 723 | 724 | samplePos += sampleInc; 725 | if (samplePos > sampleLast) 726 | samplePos = sampleLast; 727 | 728 | sy0 = SAMPLE_Y(samplePos); 729 | p.drawLine(x, sy2, x, sy0); 730 | } 731 | 732 | p.setPen(QColor(0x81,0xa0,0xb0, 0x90)); 733 | p.drawLine(0, halfH, width-1, halfH); 734 | } 735 | 736 | 737 | void SfxWindow::updateStats(const Wave* wdat) 738 | { 739 | drawWave(&_wavePix, wdat); 740 | _wavePic->setPixmap(_wavePix); 741 | 742 | _stats[0]->setText(QString::asprintf("Frames: %d", wdat->frameCount)); 743 | _stats[1]->setText(QString::asprintf("Duration: %d ms", 744 | wdat->frameCount * 1000 / wdat->sampleRate)); 745 | _stats[2]->setText(QString::asprintf("Size: %d bytes", wdat->frameCount*2)); 746 | } 747 | 748 | 749 | // Update audio buffer. 750 | void SfxWindow::regenerate(bool play) 751 | { 752 | int i = _activeWav; 753 | Wave* wdat = _wav->wave + i; 754 | 755 | int scount = sfx_generateWave(_synth, _wav->params + i); 756 | 757 | // Copy sample data to Wave struct. 758 | size_t bytes = scount * sizeof(float); 759 | if (uint32_t(scount) > wdat->frameCount) { 760 | free(wdat->data); 761 | wdat->data = (float*) malloc(bytes); 762 | } 763 | memcpy(wdat->data, _synth->samples.f, bytes); 764 | 765 | wdat->frameCount = scount; 766 | wdat->sampleRate = _synth->sampleRate; 767 | wdat->sampleSize = 32; 768 | wdat->channels = 1; 769 | 770 | // Copy sample data to audio system. 771 | #ifdef USE_FAUN 772 | faun_loadBufferPcm(i, FAUN_FMT_F32 | FAUN_FMT_MONO | FAUN_FMT_44100, 773 | _synth->samples.f, scount); 774 | if (play) 775 | faun_playSource(i, i, FAUN_PLAY_ONCE); 776 | #else 777 | if (_wav->srcId[i]) { 778 | // Must stop all as multiple sources may be attached to our buffer. 779 | aud_stopAll(); 780 | } 781 | aud_loadBufferF32(_wav->bufId[i], _synth->samples.f, scount, 0, 782 | _synth->sampleRate); 783 | if (play) 784 | _wav->srcId[i] = aud_playSound(_wav->bufId[i]); 785 | #endif 786 | 787 | updateStats(wdat); 788 | } 789 | 790 | 791 | void SfxWindow::chooseWaveSlot(int i, bool checked) 792 | { 793 | if (checked) { 794 | _activeWav = i & 3; 795 | 796 | updateParameterWidgets(_wav->params + _activeWav); 797 | 798 | const Wave* wave = _wav->wave + _activeWav; 799 | if (wave->data) { 800 | playSound(); 801 | updateStats(wave); 802 | } else 803 | regenerate(true); 804 | } 805 | } 806 | 807 | 808 | void SfxWindow::chooseWaveForm(int wform, bool checked) 809 | { 810 | if (checked) { 811 | //printf("KR wform %d\n", wform); 812 | if (_paramAssign) { 813 | _wav->params[_activeWav].waveType = wform; 814 | if (_playOnChange) 815 | regenerate(true); 816 | } 817 | } 818 | } 819 | 820 | 821 | void SfxWindow::chooseFile(const QModelIndex& mi) 822 | { 823 | QString fn = _files->filePath(mi); 824 | if (! fn.isEmpty()) { 825 | if (open(fn, false) && _playOnChange) 826 | playSound(); 827 | } 828 | } 829 | 830 | 831 | void SfxWindow::volumeChanged(int value) 832 | { 833 | float fv = float(value) * 0.01f; 834 | _paramReadout[PARAM_VOL]->setText(QString::number(fv, 'f', 2)); 835 | #ifdef USE_FAUN 836 | faun_setParameter(0, MAX_WAVE_SLOTS, FAUN_VOLUME, fv); 837 | #else 838 | aud_setSoundVolume(fv); 839 | #endif 840 | if (_playOnChange) 841 | playSound(); 842 | } 843 | 844 | 845 | void SfxWindow::paramChanged(int value) 846 | { 847 | int pid = sender()->property("pid").toInt(); 848 | float fv = float(value) * 0.0025f; 849 | _paramReadout[pid]->setText(QString::number(fv, 'f', 3)); 850 | _recentPid = pid; 851 | 852 | if (_paramAssign) { 853 | float* fval = &_wav->params[_activeWav].attackTime; 854 | fval[pid - 1] = fv; 855 | //printf( "KR pc %d %d\n", pid, value); 856 | 857 | if (_playOnChange) 858 | regenerate(true); 859 | } 860 | } 861 | 862 | 863 | //---------------------------------------------------------------------------- 864 | 865 | 866 | int main( int argc, char **argv ) 867 | { 868 | QApplication app( argc, argv ); 869 | app.setOrganizationName( APP_NAME ); 870 | app.setApplicationName( APP_NAME ); 871 | 872 | SfxWindow w; 873 | w.show(); 874 | 875 | if( argc > 1 ) 876 | w.open(argv[1], true); 877 | else 878 | w.regenerate(false); 879 | 880 | return app.exec(); 881 | } 882 | 883 | 884 | //EOF 885 | -------------------------------------------------------------------------------- /sfx_gen.c: -------------------------------------------------------------------------------- 1 | /* 2 | sfx_gen - Sound Effect Generator 3 | A stand-alone C version of DrPetter's sfxr synthesizer from 4 | http://www.drpetter.se/project_sfxr.html 5 | 6 | Copyright (c) 2007 Tomas Pettersson 7 | Copyright (c) 2022,2023 Karl Robillard 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a 10 | copy of this software and associated documentation files (the "Software"), 11 | to deal in the Software without restriction, including without limitation 12 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 13 | and/or sell copies of the Software, and to permit persons to whom the 14 | Software is furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in 17 | all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 22 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | */ 27 | 28 | #include "sfx_gen.h" 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | // This function provided by the user returns an integer between 37 | // 0 (inclusive) and range (exclusive). 38 | extern int sfx_random(int range); 39 | 40 | // If only a single output sample format is needed, it can be hardcoded 41 | // to optimize sfx_generateWave a bit. 42 | // SINGLE_FORMAT values: 1=uint8_t 2=int16_t 3=float 43 | //#define SINGLE_FORMAT 2 44 | 45 | // Apply squareDuty to sawtooth waveform. 46 | #define SAWTOOTH_DUTY 47 | 48 | #define PI 3.14159265f 49 | 50 | /* 51 | * Allocate a synth structure and sample buffer as a single block of memory. 52 | * Returns a pointer to an initialized SfxSynth structure which the caller 53 | * must free(). 54 | * 55 | * The use of this function is optional, as the user can provide their own 56 | * SfxSynth struct and buffer. 57 | */ 58 | SfxSynth* sfx_allocSynth(int format, int sampleRate, int maxDuration) 59 | { 60 | SfxSynth* syn; 61 | size_t bufLen = sampleRate * maxDuration; 62 | 63 | if (format == SFX_I16) 64 | bufLen *= sizeof(int16_t); 65 | else if (format == SFX_F32) 66 | bufLen *= sizeof(float); 67 | 68 | syn = (SfxSynth*) malloc(sizeof(SfxSynth) + bufLen); 69 | if (syn) { 70 | syn->sampleFormat = format; 71 | syn->sampleRate = sampleRate; 72 | syn->maxDuration = maxDuration; 73 | syn->samples.f = (float*) (syn + 1); 74 | } 75 | return syn; 76 | } 77 | 78 | // Return float in the range 0.0 to 1.0 (both inclusive). 79 | static float frnd(float range) 80 | { 81 | return (float)sfx_random(10001)/10000.0f*range; 82 | } 83 | 84 | // Return float in the range -1.0 to 1.0 (both inclusive). 85 | static float rndNP1() 86 | { 87 | return (float)sfx_random(20001)/10000.0f - 1.0f; 88 | } 89 | 90 | #define PINK_SIZE 5 91 | 92 | // Return -1.0 to 1.0. 93 | static float pinkValue(int* pinkI, float* whiteValue) 94 | { 95 | float sum = 0.0; 96 | int bitsChanged; 97 | int lastI = *pinkI; 98 | int i = lastI + 1; 99 | if (i > 0x1f) // Number of set bits matches PINK_SIZE. 100 | i = 0; 101 | bitsChanged = lastI ^ i; 102 | *pinkI = i; 103 | 104 | for (i = 0; i < PINK_SIZE; ++i) { 105 | if (bitsChanged & (1 << i)) 106 | whiteValue[i] = frnd(1.0f); 107 | sum += whiteValue[i]; 108 | } 109 | return (sum/PINK_SIZE) * 2.0f - 1.0f; 110 | } 111 | 112 | /* 113 | * Synthesize wave data from parameters. 114 | * A 44100Hz, mono channel wave is generated. 115 | * 116 | * Return the number of samples generated. 117 | */ 118 | int sfx_generateWave(SfxSynth* synth, const SfxParams* sp) 119 | { 120 | float* phaserBuffer = synth->phaserBuffer; 121 | float* noiseBuffer = synth->noiseBuffer; 122 | int phase = 0; 123 | double fperiod; 124 | double fmaxperiod; 125 | double fslide; 126 | double fdslide; 127 | int period; 128 | float squareDuty; 129 | float squareSlide; 130 | int envStage; 131 | int envTime; 132 | int envLength[3]; 133 | float envVolume; 134 | float fphase; 135 | float fdphase; 136 | int iphase; 137 | int ipp; 138 | float fltp; 139 | float fltdp; 140 | float fltw; 141 | float fltwd; 142 | float fltdmp; 143 | float fltphp; 144 | float flthp; 145 | float flthpd; 146 | float vibratoPhase; 147 | float vibratoSpeed; 148 | float vibratoAmplitude; 149 | int repeatTime; 150 | int repeatLimit; 151 | int arpeggioTime; 152 | int arpeggioLimit; 153 | double arpeggioModulation; 154 | float minFreq, sslide; 155 | int pinkI; 156 | int i, sampleCount; 157 | 158 | 159 | // Sanity check some related parameters. 160 | minFreq = sp->minFrequency; 161 | if (minFreq > sp->startFrequency) 162 | minFreq = sp->startFrequency; 163 | 164 | sslide = sp->slide; 165 | if (sslide < sp->deltaSlide) 166 | sslide = sp->deltaSlide; 167 | 168 | 169 | #define RESET_SAMPLE \ 170 | fperiod = 100.0/(sp->startFrequency*sp->startFrequency + 0.001); \ 171 | period = (int)fperiod; \ 172 | fmaxperiod = 100.0/(minFreq * minFreq + 0.001); \ 173 | fslide = 1.0 - pow((double)sslide, 3.0)*0.01; \ 174 | fdslide = -pow((double)sp->deltaSlide, 3.0)*0.000001; \ 175 | squareDuty = 0.5f - sp->squareDuty*0.5f; \ 176 | squareSlide = -sp->dutySweep*0.00005f; \ 177 | arpeggioModulation = (sp->changeAmount >= 0.0f) ? \ 178 | 1.0 - pow((double)sp->changeAmount, 2.0)*0.9 : \ 179 | 1.0 + pow((double)sp->changeAmount, 2.0)*10.0; \ 180 | arpeggioTime = 0; \ 181 | arpeggioLimit = (sp->changeSpeed == 1.0f) ? 0 : \ 182 | (int)(powf(1.0f - sp->changeSpeed, 2.0f)*20000 + 32); 183 | 184 | 185 | RESET_SAMPLE 186 | 187 | // Reset filter 188 | fltp = fltdp = 0.0f; 189 | fltw = powf(sp->lpfCutoff, 3.0f)*0.1f; 190 | fltwd = 1.0f + sp->lpfCutoffSweep*0.0001f; 191 | fltdmp = 5.0f/(1.0f + powf(sp->lpfResonance, 2.0f)*20.0f)*(0.01f + fltw); 192 | if (fltdmp > 0.8f) 193 | fltdmp = 0.8f; 194 | fltphp = 0.0f; 195 | flthp = powf(sp->hpfCutoff, 2.0f)*0.1f; 196 | flthpd = 1.0f + sp->hpfCutoffSweep*0.0003f; 197 | 198 | // Reset vibrato 199 | vibratoPhase = 0.0f; 200 | vibratoSpeed = powf(sp->vibratoSpeed, 2.0f)*0.01f; 201 | vibratoAmplitude = sp->vibratoDepth*0.5f; 202 | 203 | // Reset envelope 204 | envVolume = 0.0f; 205 | envStage = envTime = 0; 206 | envLength[0] = (int)(sp->attackTime *sp->attackTime *100000.0f); 207 | envLength[1] = (int)(sp->sustainTime*sp->sustainTime*100000.0f); 208 | envLength[2] = (int)(sp->decayTime *sp->decayTime *100000.0f); 209 | 210 | fphase = powf(sp->phaserOffset, 2.0f)*1020.0f; 211 | if (sp->phaserOffset < 0.0f) 212 | fphase = -fphase; 213 | 214 | fdphase = powf(sp->phaserSweep, 2.0f)*1.0f; 215 | if (sp->phaserSweep < 0.0f) 216 | fdphase = -fdphase; 217 | 218 | iphase = abs((int)fphase); 219 | ipp = 0; 220 | for (i = 0; i < 1024; i++) 221 | phaserBuffer[i] = 0.0f; 222 | 223 | if (sp->waveType == SFX_PINK_NOISE) { 224 | pinkI = 0; 225 | for (i = 0; i < PINK_SIZE; i++) 226 | synth->pinkWhiteValue[i] = frnd(1.0f); 227 | } 228 | 229 | #define RESET_NOISE \ 230 | if (sp->waveType == SFX_NOISE) { \ 231 | for (i = 0; i < 32; i++) \ 232 | noiseBuffer[i] = rndNP1(); \ 233 | } else if (sp->waveType == SFX_PINK_NOISE) { \ 234 | for (i = 0; i < 32; i++) \ 235 | noiseBuffer[i] = pinkValue(&pinkI, synth->pinkWhiteValue); \ 236 | } 237 | 238 | RESET_NOISE 239 | 240 | repeatTime = 0; 241 | repeatLimit = (int)(powf(1.0f - sp->repeatSpeed, 2.0f)*20000 + 32); 242 | if (sp->repeatSpeed == 0.0f) 243 | repeatLimit = 0; 244 | 245 | 246 | // Synthesize samples. 247 | { 248 | const float sampleCoefficient = 0.2f; // Scales sample value to [-1..1] 249 | #if SINGLE_FORMAT == 1 250 | uint8_t* buffer = synth->samples.u8; 251 | #elif SINGLE_FORMAT == 2 252 | int16_t* buffer = synth->samples.i16; 253 | #else 254 | float* buffer = synth->samples.f; 255 | #endif 256 | float ssample, rfperiod, fp, pp; 257 | int sampleEnd = synth->sampleRate * synth->maxDuration; 258 | int si; 259 | 260 | for (sampleCount = 0; sampleCount < sampleEnd; sampleCount++) 261 | { 262 | repeatTime++; 263 | if (repeatLimit != 0 && repeatTime >= repeatLimit) { 264 | repeatTime = 0; 265 | RESET_SAMPLE 266 | } 267 | 268 | // Frequency envelopes/arpeggios 269 | arpeggioTime++; 270 | 271 | if ((arpeggioLimit != 0) && (arpeggioTime >= arpeggioLimit)) { 272 | arpeggioLimit = 0; 273 | fperiod *= arpeggioModulation; 274 | } 275 | 276 | fslide += fdslide; 277 | fperiod *= fslide; 278 | 279 | if (fperiod > fmaxperiod) { 280 | fperiod = fmaxperiod; 281 | if (minFreq > 0.0f) 282 | sampleEnd = sampleCount; // End generator loop. 283 | } 284 | 285 | rfperiod = (float)fperiod; 286 | 287 | if (vibratoAmplitude > 0.0f) { 288 | vibratoPhase += vibratoSpeed; 289 | rfperiod = (float) 290 | (fperiod * (1.0 + sinf(vibratoPhase) * vibratoAmplitude)); 291 | } 292 | 293 | period = (int)rfperiod; 294 | if (period < 8) 295 | period = 8; 296 | 297 | squareDuty += squareSlide; 298 | if (squareDuty < 0.0f) 299 | squareDuty = 0.0f; 300 | else if (squareDuty > 0.5f) 301 | squareDuty = 0.5f; 302 | 303 | // Volume envelope 304 | envTime++; 305 | if (envTime > envLength[envStage]) { 306 | envTime = 0; 307 | next_stage: 308 | envStage++; 309 | if (envStage == 3) 310 | break; // End generator loop. 311 | if (envLength[envStage] == 0) 312 | goto next_stage; 313 | } 314 | 315 | switch (envStage) { 316 | case 0: 317 | envVolume = (float)envTime/envLength[0]; 318 | break; 319 | case 1: 320 | envVolume = 1.0f + powf(1.0f - (float)envTime/envLength[1], 1.0f) * 2.0f * sp->sustainPunch; 321 | break; 322 | case 2: 323 | envVolume = 1.0f - (float)envTime/envLength[2]; 324 | break; 325 | } 326 | 327 | // Phaser step 328 | fphase += fdphase; 329 | iphase = abs((int)fphase); 330 | 331 | if (iphase > 1023) 332 | iphase = 1023; 333 | 334 | if (flthpd != 0.0f) { 335 | flthp *= flthpd; 336 | if (flthp < 0.00001f) 337 | flthp = 0.00001f; 338 | else if (flthp > 0.1f) 339 | flthp = 0.1f; 340 | } 341 | 342 | // 8x supersampling 343 | ssample = 0.0f; 344 | for (si = 0; si < 8; si++) { 345 | float sample = 0.0f; 346 | phase++; 347 | 348 | if (phase >= period) { 349 | //phase = 0; 350 | phase %= period; 351 | 352 | RESET_NOISE 353 | } 354 | 355 | // Base waveform 356 | fp = (float)phase/period; 357 | 358 | #define RAMP(v, x1, x2, y1, y2) (y1 + (y2 - y1) * ((v - x1) / (x2 - x1))) 359 | 360 | switch (sp->waveType) { 361 | case SFX_SQUARE: 362 | sample = (fp < squareDuty) ? 0.5f : -0.5f; 363 | break; 364 | case SFX_SAWTOOTH: 365 | #ifdef SAWTOOTH_DUTY 366 | sample = (fp < squareDuty) ? 367 | -1.0f + 2.0f * fp/squareDuty : 368 | 1.0f - 2.0f * (fp-squareDuty)/(1.0f-squareDuty); 369 | #else 370 | sample = 1.0f - fp*2; 371 | #endif 372 | break; 373 | case SFX_SINE: 374 | sample = sinf(fp*2*PI); 375 | break; 376 | case SFX_NOISE: 377 | case SFX_PINK_NOISE: 378 | sample = noiseBuffer[phase*32/period]; 379 | break; 380 | case SFX_TRIANGLE: 381 | sample = (fp < 0.5) ? RAMP(fp, 0.0f, 0.5f, -1.0f, 1.0f) : 382 | RAMP(fp, 0.5f, 1.0f, 1.0f, -1.0f); 383 | break; 384 | } 385 | 386 | // Low-pass filter 387 | pp = fltp; 388 | fltw *= fltwd; 389 | 390 | if (fltw < 0.0f) 391 | fltw = 0.0f; 392 | else if (fltw > 0.1f) 393 | fltw = 0.1f; 394 | 395 | if (sp->lpfCutoff != 1.0f) { 396 | fltdp += (sample-fltp)*fltw; 397 | fltdp -= fltdp*fltdmp; 398 | } else { 399 | fltp = sample; 400 | fltdp = 0.0f; 401 | } 402 | 403 | fltp += fltdp; 404 | 405 | // High-pass filter 406 | fltphp += fltp - pp; 407 | fltphp -= fltphp*flthp; 408 | sample = fltphp; 409 | 410 | // Phaser 411 | phaserBuffer[ipp & 1023] = sample; 412 | sample += phaserBuffer[(ipp - iphase + 1024) & 1023]; 413 | ipp = (ipp + 1) & 1023; 414 | 415 | // Final accumulation and envelope application 416 | ssample += sample*envVolume; 417 | } 418 | 419 | ssample = ssample/8 * sampleCoefficient; 420 | 421 | // Clamp sample and emit to buffer 422 | if (ssample > 1.0f) 423 | ssample = 1.0f; 424 | else if (ssample < -1.0f) 425 | ssample = -1.0f; 426 | 427 | //printf("%d %f\n", sampleCount, ssample); 428 | #if SINGLE_FORMAT == 1 429 | *buffer++ = (uint8_t) (ssample*127.0f + 128.0f); 430 | #elif SINGLE_FORMAT == 2 431 | *buffer++ = (int16_t) (ssample*32767.0f); 432 | #elif SINGLE_FORMAT == 3 433 | *buffer++ = ssample; 434 | #else 435 | switch (synth->sampleFormat) { 436 | case SFX_U8: 437 | ((uint8_t*)buffer)[sampleCount] = (uint8_t) 438 | (ssample*127.0f + 128.0f); 439 | break; 440 | case SFX_I16: 441 | ((int16_t*)buffer)[sampleCount] = (int16_t) (ssample*32767.0f); 442 | break; 443 | case SFX_F32: 444 | buffer[sampleCount] = ssample; 445 | break; 446 | } 447 | #endif 448 | } 449 | } 450 | 451 | return sampleCount; 452 | } 453 | 454 | #ifndef CONFIG_SFX_NO_FILEIO 455 | //---------------------------------------------------------------------------- 456 | // Load/Save functions 457 | 458 | /* 459 | * Load an rFXGen (.rfx) or sfxr settings file. 460 | * Returns error message or NULL if load was successful. 461 | */ 462 | const char* sfx_loadParams(SfxParams *sp, const char *fileName, 463 | float* sfsVolume) 464 | { 465 | union { 466 | int32_t version; 467 | char signature[4]; 468 | } header; 469 | size_t n; 470 | const char *err = "File read failed"; 471 | FILE *fp = fopen(fileName, "rb"); 472 | if (fp == NULL) 473 | return "File open failed"; 474 | 475 | n = fread(&header, 1, 4, fp); 476 | if (n != 4) 477 | goto cleanup; 478 | 479 | // Check for .rfx file signature. 480 | if ((header.signature[0] == 'r') && 481 | (header.signature[1] == 'F') && 482 | (header.signature[2] == 'X') && 483 | (header.signature[3] == ' ')) 484 | { 485 | uint16_t version, length; 486 | fread(&version, 1, sizeof(uint16_t), fp); 487 | fread(&length, 1, sizeof(uint16_t), fp); 488 | 489 | if (version != 200) 490 | err = "rFX file version not supported"; 491 | else if (length != sizeof(SfxParams)) 492 | err = "Invalid rFX wave parameters size"; 493 | else { 494 | // Load all parameters. 495 | n = fread(sp, 1, sizeof(SfxParams), fp); 496 | if (n == sizeof(SfxParams)) 497 | err = NULL; 498 | } 499 | } else { 500 | // Load sfxr settings. Note that vibratoPhaseDelay & filterOn are 501 | // unused in the original sfxr code. 502 | 503 | float volume = 0.5f; 504 | float vibratoPhaseDelay; 505 | char filterOn; 506 | int version = header.version; 507 | 508 | if ((version == 100) || (version == 101) || (version == 102)) 509 | { 510 | fread(&sp->waveType, 1, sizeof(int), fp); 511 | 512 | if (version == 102) 513 | fread(&volume, 1, sizeof(float), fp); 514 | if (sfsVolume) 515 | *sfsVolume = volume; 516 | 517 | fread(&sp->startFrequency, 1, sizeof(float), fp); 518 | fread(&sp->minFrequency, 1, sizeof(float), fp); 519 | fread(&sp->slide, 1, sizeof(float), fp); 520 | 521 | if (version >= 101) 522 | fread(&sp->deltaSlide, 1, sizeof(float), fp); 523 | else 524 | sp->deltaSlide = 0.0f; 525 | 526 | fread(&sp->squareDuty, 1, sizeof(float), fp); 527 | fread(&sp->dutySweep, 1, sizeof(float), fp); 528 | 529 | fread(&sp->vibratoDepth, 1, sizeof(float), fp); 530 | fread(&sp->vibratoSpeed, 1, sizeof(float), fp); 531 | fread(&vibratoPhaseDelay, 1, sizeof(float), fp); 532 | 533 | fread(&sp->attackTime, 1, sizeof(float), fp); 534 | fread(&sp->sustainTime, 1, sizeof(float), fp); 535 | fread(&sp->decayTime, 1, sizeof(float), fp); 536 | fread(&sp->sustainPunch, 1, sizeof(float), fp); 537 | 538 | fread(&filterOn, 1, 1, fp); 539 | fread(&sp->lpfResonance, 1, sizeof(float), fp); 540 | fread(&sp->lpfCutoff, 1, sizeof(float), fp); 541 | fread(&sp->lpfCutoffSweep, 1, sizeof(float), fp); 542 | fread(&sp->hpfCutoff, 1, sizeof(float), fp); 543 | fread(&sp->hpfCutoffSweep, 1, sizeof(float), fp); 544 | 545 | fread(&sp->phaserOffset, 1, sizeof(float), fp); 546 | fread(&sp->phaserSweep, 1, sizeof(float), fp); 547 | fread(&sp->repeatSpeed, 1, sizeof(float), fp); 548 | 549 | if (version >= 101) { 550 | fread(&sp->changeSpeed, 1, sizeof(float), fp); 551 | fread(&sp->changeAmount, 1, sizeof(float), fp); 552 | } else { 553 | sp->changeSpeed = 554 | sp->changeAmount = 0.0f; 555 | } 556 | err = NULL; 557 | } 558 | else 559 | err = "SFS file version not supported"; 560 | } 561 | 562 | cleanup: 563 | fclose(fp); 564 | return err; 565 | } 566 | 567 | /* 568 | * Save rFXGen (.rfx) sound parameters file. 569 | * Returns error message or NULL if save was successful. 570 | */ 571 | const char* sfx_saveRfx(const SfxParams *sp, const char *fileName) 572 | { 573 | FILE *fp; 574 | size_t n; 575 | uint16_t version = 200; 576 | uint16_t dataLen = 96; 577 | 578 | assert(sizeof(SfxParams) == 96); 579 | 580 | fp = fopen(fileName, "wb"); 581 | if (fp == NULL) 582 | return "File open failed"; 583 | 584 | // Write .rfx file header. 585 | fwrite("rFX ", 1, 4, fp); 586 | fwrite(&version, 1, sizeof(uint16_t), fp); 587 | fwrite(&dataLen, 1, sizeof(uint16_t), fp); 588 | 589 | // Write sound parameters. 590 | n = fwrite(sp, 1, dataLen, fp); 591 | fclose(fp); 592 | 593 | if (n != dataLen) 594 | return "File write failed"; 595 | return NULL; 596 | } 597 | #endif 598 | 599 | #ifndef CONFIG_SFX_NO_GENERATORS 600 | //---------------------------------------------------------------------------- 601 | /* 602 | * Parameter generator functions 603 | * 604 | * If randSeed is being used the caller is responsible for seeding the 605 | * random number generator before the call and setting the variable after it. 606 | */ 607 | 608 | /* 609 | * Reset sound parameters to a default square wave. 610 | * The randSeed is set to zero. 611 | */ 612 | void sfx_resetParams(SfxParams *sp) 613 | { 614 | sp->randSeed = 0; 615 | sp->waveType = SFX_SQUARE; 616 | 617 | // Wave envelope parameters 618 | sp->attackTime = 0.0f; 619 | sp->sustainTime = 0.3f; 620 | sp->sustainPunch = 0.0f; 621 | sp->decayTime = 0.4f; 622 | 623 | // Frequency parameters 624 | sp->startFrequency = 0.3f; 625 | sp->minFrequency = 0.0f; 626 | sp->slide = 0.0f; 627 | sp->deltaSlide = 0.0f; 628 | sp->vibratoDepth = 0.0f; 629 | sp->vibratoSpeed = 0.0f; 630 | //sp->vibratoPhaseDelay = 0.0f; 631 | 632 | // Tone change parameters 633 | sp->changeAmount = 0.0f; 634 | sp->changeSpeed = 0.0f; 635 | 636 | // Square wave parameters 637 | sp->squareDuty = 0.0f; 638 | sp->dutySweep = 0.0f; 639 | 640 | // Repeat parameters 641 | sp->repeatSpeed = 0.0f; 642 | 643 | // Phaser parameters 644 | sp->phaserOffset = 0.0f; 645 | sp->phaserSweep = 0.0f; 646 | 647 | // Filter parameters 648 | sp->lpfCutoff = 1.0f; 649 | sp->lpfCutoffSweep = 0.0f; 650 | sp->lpfResonance = 0.0f; 651 | sp->hpfCutoff = 0.0f; 652 | sp->hpfCutoffSweep = 0.0f; 653 | } 654 | 655 | void sfx_genPickupCoin(SfxParams* sp) 656 | { 657 | sfx_resetParams(sp); 658 | 659 | sp->startFrequency = 0.4f + frnd(0.5f); 660 | sp->attackTime = 0.0f; 661 | sp->sustainTime = frnd(0.1f); 662 | sp->decayTime = 0.1f + frnd(0.4f); 663 | sp->sustainPunch = 0.3f + frnd(0.3f); 664 | 665 | if (sfx_random(2)) { 666 | sp->changeSpeed = 0.5f + frnd(0.2f); 667 | sp->changeAmount = 0.2f + frnd(0.4f); 668 | } 669 | } 670 | 671 | void sfx_genLaserShoot(SfxParams* sp) 672 | { 673 | sfx_resetParams(sp); 674 | 675 | sp->waveType = sfx_random(3); 676 | 677 | if ((sp->waveType == SFX_SINE) && sfx_random(2)) 678 | sp->waveType = sfx_random(2); 679 | 680 | sp->startFrequency = 0.5f + frnd(0.5f); 681 | sp->minFrequency = sp->startFrequency - 0.2f - frnd(0.6f); 682 | 683 | if (sp->minFrequency < 0.2f) 684 | sp->minFrequency = 0.2f; 685 | 686 | sp->slide = -0.15f - frnd(0.2f); 687 | 688 | if (sfx_random(3) == 0) { 689 | sp->startFrequency = 0.3f + frnd(0.6f); 690 | sp->minFrequency = frnd(0.1f); 691 | sp->slide = -0.35f - frnd(0.3f); 692 | } 693 | 694 | if (sfx_random(2)) { 695 | sp->squareDuty = frnd(0.5f); 696 | sp->dutySweep = frnd(0.2f); 697 | } else { 698 | sp->squareDuty = 0.4f + frnd(0.5f); 699 | sp->dutySweep = -frnd(0.7f); 700 | } 701 | 702 | sp->attackTime = 0.0f; 703 | sp->sustainTime = 0.1f + frnd(0.2f); 704 | sp->decayTime = frnd(0.4f); 705 | 706 | if (sfx_random(2)) 707 | sp->sustainPunch = frnd(0.3f); 708 | 709 | if (sfx_random(3) == 0) { 710 | sp->phaserOffset = frnd(0.2f); 711 | sp->phaserSweep = -frnd(0.2f); 712 | } 713 | 714 | if (sfx_random(2)) 715 | sp->hpfCutoff = frnd(0.3f); 716 | } 717 | 718 | void sfx_genExplosion(SfxParams* sp) 719 | { 720 | sfx_resetParams(sp); 721 | 722 | sp->waveType = SFX_NOISE; 723 | 724 | if (sfx_random(2)) { 725 | sp->startFrequency = 0.1f + frnd(0.4f); 726 | sp->slide = -0.1f + frnd(0.4f); 727 | } else { 728 | sp->startFrequency = 0.2f + frnd(0.7f); 729 | sp->slide = -0.2f - frnd(0.2f); 730 | } 731 | 732 | sp->startFrequency *= sp->startFrequency; 733 | 734 | if (sfx_random(5) == 0) 735 | sp->slide = 0.0f; 736 | if (sfx_random(3) == 0) 737 | sp->repeatSpeed = 0.3f + frnd(0.5f); 738 | 739 | sp->attackTime = 0.0f; 740 | sp->sustainTime = 0.1f + frnd(0.3f); 741 | sp->decayTime = frnd(0.5f); 742 | 743 | if (sfx_random(2) == 0) { 744 | sp->phaserOffset = -0.3f + frnd(0.9f); 745 | sp->phaserSweep = -frnd(0.3f); 746 | } 747 | 748 | sp->sustainPunch = 0.2f + frnd(0.6f); 749 | 750 | if (sfx_random(2)) { 751 | sp->vibratoDepth = frnd(0.7f); 752 | sp->vibratoSpeed = frnd(0.6f); 753 | } 754 | 755 | if (sfx_random(3) == 0) { 756 | sp->changeSpeed = 0.6f + frnd(0.3f); 757 | sp->changeAmount = 0.8f - frnd(1.6f); 758 | } 759 | } 760 | 761 | void sfx_genPowerup(SfxParams* sp) 762 | { 763 | sfx_resetParams(sp); 764 | 765 | if (sfx_random(2)) { 766 | sp->waveType = SFX_SAWTOOTH; 767 | #ifdef SAWTOOTH_DUTY 768 | sp->squareDuty = 1.0f; 769 | #endif 770 | } else 771 | sp->squareDuty = frnd(0.6f); 772 | 773 | if (sfx_random(2)) { 774 | sp->startFrequency = 0.2f + frnd(0.3f); 775 | sp->slide = 0.1f + frnd(0.4f); 776 | sp->repeatSpeed = 0.4f + frnd(0.4f); 777 | } else { 778 | sp->startFrequency = 0.2f + frnd(0.3f); 779 | sp->slide = 0.05f + frnd(0.2f); 780 | 781 | if (sfx_random(2)) { 782 | sp->vibratoDepth = frnd(0.7f); 783 | sp->vibratoSpeed = frnd(0.6f); 784 | } 785 | } 786 | 787 | sp->attackTime = 0.0f; 788 | sp->sustainTime = frnd(0.4f); 789 | sp->decayTime = 0.1f + frnd(0.4f); 790 | } 791 | 792 | void sfx_genHitHurt(SfxParams* sp) 793 | { 794 | sfx_resetParams(sp); 795 | 796 | sp->waveType = sfx_random(3); 797 | if (sp->waveType == SFX_SINE) 798 | sp->waveType = SFX_NOISE; 799 | else if (sp->waveType == SFX_SQUARE) 800 | sp->squareDuty = frnd(0.6f); 801 | #ifdef SAWTOOTH_DUTY 802 | else if (sp->waveType == SFX_SAWTOOTH) 803 | sp->squareDuty = 1.0f; 804 | #endif 805 | 806 | sp->startFrequency = 0.2f + frnd(0.6f); 807 | sp->slide = -0.3f - frnd(0.4f); 808 | sp->attackTime = 0.0f; 809 | sp->sustainTime = frnd(0.1f); 810 | sp->decayTime = 0.1f + frnd(0.2f); 811 | 812 | if (sfx_random(2)) 813 | sp->hpfCutoff = frnd(0.3f); 814 | } 815 | 816 | void sfx_genJump(SfxParams* sp) 817 | { 818 | sfx_resetParams(sp); 819 | 820 | sp->waveType = SFX_SQUARE; 821 | sp->squareDuty = frnd(0.6f); 822 | sp->startFrequency = 0.3f + frnd(0.3f); 823 | sp->slide = 0.1f + frnd(0.2f); 824 | sp->attackTime = 0.0f; 825 | sp->sustainTime = 0.1f + frnd(0.3f); 826 | sp->decayTime = 0.1f + frnd(0.2f); 827 | 828 | if (sfx_random(2)) 829 | sp->hpfCutoff = frnd(0.3f); 830 | if (sfx_random(2)) 831 | sp->lpfCutoff = 1.0f - frnd(0.6f); 832 | } 833 | 834 | void sfx_genBlipSelect(SfxParams* sp) 835 | { 836 | sfx_resetParams(sp); 837 | 838 | sp->waveType = sfx_random(2); 839 | if (sp->waveType == SFX_SQUARE) 840 | sp->squareDuty = frnd(0.6f); 841 | #ifdef SAWTOOTH_DUTY 842 | else 843 | sp->squareDuty = 1.0f; 844 | #endif 845 | sp->startFrequency = 0.2f + frnd(0.4f); 846 | sp->attackTime = 0.0f; 847 | sp->sustainTime = 0.1f + frnd(0.1f); 848 | sp->decayTime = frnd(0.2f); 849 | sp->hpfCutoff = 0.1f; 850 | } 851 | 852 | void sfx_genSynth(SfxParams* sp) 853 | { 854 | static const float synthFreq[3] = { 855 | 0.27231713609, 0.19255692561, 0.13615778746 856 | }; 857 | static const float arpeggioMod[7] = { 858 | 0, 0, 0, 0, -0.3162, 0.7454, 0.7454 859 | }; 860 | 861 | sfx_resetParams(sp); 862 | 863 | sp->waveType = sfx_random(2); 864 | sp->startFrequency = synthFreq[ sfx_random(3) ]; 865 | sp->attackTime = sfx_random(5) > 3 ? frnd(0.5) : 0; 866 | sp->sustainTime = frnd(1.0f); 867 | sp->sustainPunch = frnd(1.0f); 868 | sp->decayTime = frnd(0.9f) + 0.1f; 869 | sp->changeAmount = arpeggioMod[ sfx_random(7) ]; 870 | sp->changeSpeed = frnd(0.5f) + 0.4f; 871 | sp->squareDuty = frnd(1.0f); 872 | sp->dutySweep = (sfx_random(3) == 2) ? frnd(1.0f) : 0.0f; 873 | sp->lpfCutoff = (sfx_random(2) == 1) ? 1.0f : 874 | 0.9f * frnd(1.0f) * frnd(1.0f) + 0.1f; 875 | sp->lpfCutoffSweep = rndNP1(); 876 | sp->lpfResonance = frnd(1.0f); 877 | sp->hpfCutoff = (sfx_random(4) == 3) ? frnd(1.0f) : 0.0f; 878 | sp->hpfCutoffSweep = (sfx_random(4) == 3) ? frnd(1.0f) : 0.0f; 879 | } 880 | 881 | /* 882 | * Generate random sound. 883 | */ 884 | void sfx_genRandomize(SfxParams* sp, int waveType) 885 | { 886 | sfx_resetParams(sp); 887 | sp->waveType = waveType; 888 | 889 | sp->startFrequency = powf(rndNP1(), 2.0f); 890 | 891 | if (sfx_random(1)) 892 | sp->startFrequency = powf(rndNP1(), 3.0f) + 0.5f; 893 | 894 | sp->minFrequency = 0.0f; 895 | sp->slide = powf(rndNP1(), 5.0f); 896 | 897 | if ((sp->startFrequency > 0.7f) && (sp->slide > 0.2f)) 898 | sp->slide = -sp->slide; 899 | if ((sp->startFrequency < 0.2f) && (sp->slide < -0.05f)) 900 | sp->slide = -sp->slide; 901 | 902 | sp->deltaSlide = powf(rndNP1(), 3.0f); 903 | sp->squareDuty = rndNP1(); 904 | sp->dutySweep = powf(rndNP1(), 3.0f); 905 | sp->vibratoDepth = powf(rndNP1(), 3.0f); 906 | sp->vibratoSpeed = rndNP1(); 907 | //sp->vibratoPhaseDelay = rndNP1(); 908 | sp->attackTime = powf(rndNP1(), 3.0f); 909 | sp->sustainTime = powf(rndNP1(), 2.0f); 910 | sp->decayTime = rndNP1(); 911 | sp->sustainPunch = powf(frnd(0.8f), 2.0f); 912 | 913 | if (sp->attackTime + sp->sustainTime + sp->decayTime < 0.2f) 914 | { 915 | sp->sustainTime += 0.2f + frnd(0.3f); 916 | sp->decayTime += 0.2f + frnd(0.3f); 917 | } 918 | 919 | sp->lpfResonance = rndNP1(); 920 | sp->lpfCutoff = 1.0f - powf(frnd(1.0f), 3.0f); 921 | sp->lpfCutoffSweep = powf(rndNP1(), 3.0f); 922 | 923 | if (sp->lpfCutoff < 0.1f && sp->lpfCutoffSweep < -0.05f) 924 | sp->lpfCutoffSweep = -sp->lpfCutoffSweep; 925 | 926 | sp->hpfCutoff = powf(frnd(1.0f), 5.0f); 927 | sp->hpfCutoffSweep = powf(rndNP1(), 5.0f); 928 | sp->phaserOffset = powf(rndNP1(), 3.0f); 929 | sp->phaserSweep = powf(rndNP1(), 3.0f); 930 | sp->repeatSpeed = rndNP1(); 931 | sp->changeSpeed = rndNP1(); 932 | sp->changeAmount = rndNP1(); 933 | } 934 | 935 | /* 936 | * Mutate parameters 937 | * 938 | * The sfxr values are sfx_mutate(params, 0.1f, 0xffffdf), where minFrequency 939 | * is excluded. 940 | */ 941 | void sfx_mutate(SfxParams *sp, float range, uint32_t mask) 942 | { 943 | float* valPtr = &sp->attackTime; 944 | float half = range * 0.5f; 945 | float val, low; 946 | uint32_t rmod, bit; 947 | int i; 948 | 949 | rmod = 1 + sfx_random(0xFFFFFF); 950 | for (i = 0; i < 22; ++i) { 951 | bit = 1 << i; 952 | if ((rmod & bit) & mask) { 953 | low = (SFX_NEGATIVE_ONE_MASK & bit) ? -1.0f : 0.0f; 954 | val = *valPtr + frnd(range) - half; 955 | if (val > 1.0f) 956 | val = 1.0f; 957 | else if (val < low) 958 | val = low; 959 | *valPtr = val; 960 | } 961 | ++valPtr; 962 | } 963 | } 964 | #endif 965 | --------------------------------------------------------------------------------