├── 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 | 
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 |
--------------------------------------------------------------------------------