/******************************************************************************************* * * rFXGen 1.0 - raylib FX sounds generator (based on Tomas Petterson sfxr) * * DEPENDENCIES: * * raylib 1.6 - This program uses latest raylib audio module functionality. * raygui 1.0 - Simple IMGUI library (based on raylib) * tinyfiledialogs 2.5.8 - Open/save file dialogs, it requires linkage with comdlg32 and ole32 libs. * * VERSIONS HISTORY: * * 1.0 (XX-Sep-2016) TODO: Release stable version... not yet... * 0.95 (14-Sep-2016) Reviewed comments and .rfx format * 0.9 (12-Sep-2016) Defined WaveParams struct and command line functionality * 0.8 (09-Sep-2016) Added open/save file dialogs using tinyfiledialogs library * 0.7 (04-Sep-2016) Program variables renaming for consistency, code reorganized * 0.6 (30-Aug-2016) Interface redesigned (reduced size) and new features added (wave drawing) * 0.5 (27-Aug-2016) Completed port and adaptation from sfxr (only sound generation and playing) * * LICENSE: zlib/libpng * * Copyright (c) 2014-2016 Ramon Santamaria (@raysan5) * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, including commercial * applications, and to alter it and redistribute it freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not claim that you * wrote the original software. If you use this software in a product, an acknowledgment * in the product documentation would be appreciated but is not required. * * 2. Altered source versions must be plainly marked as such, and must not be misrepresented * as being the original software. * * 3. This notice may not be removed or altered from any source distribution. * **********************************************************************************************/ #include "raylib.h" //#define RAYGUI_STYLE_DEFAULT_DARK #define RAYGUI_NO_STYLE_SAVE_LOAD // Avoid compiling style load/save code #define RAYGUI_IMPLEMENTATION #include "raygui.h" #include "external/tinyfiledialogs.h" // Required for native open/save file dialogs #include // Required for: sinf(), pow() #include // Required for: malloc(), free() #include // Required for: strcmp() #include // Required for: FILE, fopen(), fread(), fwrite(), ftell(), fseek() fclose() // NOTE: Used on functions: LoadSound(), SaveSound(), WriteWAV() #include "twitter.h" // Twitter icon embedded #if defined(_WIN32) #include #define GetCurrentDir _getcwd #else #include #define GetCurrentDir getcwd #endif //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- #define MAX_WAVE_LENGTH 10 // 10 seconds #define rnd(n) GetRandomValue(0, n) #define frnd(range) ((float)rnd(10000)/10000.0f*range) // Wave parameters type typedef struct WaveParams { // Wave type (square, sawtooth, sine, noise) int waveTypeValue; // Wave envelope parameters float attackTimeValue; float sustainTimeValue; float sustainPunchValue; float decayTimeValue; // Frequency parameters float startFrequencyValue; float minFrequencyValue; float slideValue; float deltaSlideValue; float vibratoDepthValue; float vibratoSpeedValue; //float vibratoPhaseDelayValue; // Tone change parameters float changeAmountValue; float changeSpeedValue; // Square wave parameters float squareDutyValue; float dutySweepValue; // Repeat parameters float repeatSpeedValue; // Phaser parameters float phaserOffsetValue; float phaserSweepValue; // Filter parameters float lpfCutoffValue; float lpfCutoffSweepValue; float lpfResonanceValue; float hpfCutoffValue; float hpfCutoffSweepValue; //bool filterOn; } WaveParams; //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- // Volume parameters static float volumeValue = 0.6f; // Volume const float masterVolume = 0.15f; // Master volume // Export WAV variables static int wavBitrate = 16; // Wave bitrate (sample size in bits) static int wavFrequency = 44100; // Wave frequency (sample rate) // Wave and Sound variables static Wave wave; // Stores wave data static WaveParams params; // Stores wave parameters for generation static Sound sound; // Stores sound data (OpenAL) static char currentPath[256]; // Path to current working folder //---------------------------------------------------------------------------------- // Module Functions Declaration //---------------------------------------------------------------------------------- static void ResetParams(void); // Reset sound parameters static void GenerateWave(WaveParams params); // Generate wave data and update sound static void GeneratePlay(void); // Generate wave data and play sound static void LoadSoundParams(const char *fileName); // Load sound parameters from file static void SaveSoundParams(const char *fileName); // Save sound parameters to file static void SaveWAV(const char *fileName, Wave wave); // Export sound to .wav file static void DrawWave(Rectangle bounds, Color color); // Draw wave data using lines static const char *GetExtension(const char *fileName); // Get extension from filename // Buttons functions static void BtnPickupCoin(void); // Generate sound: Pickup/Coin static void BtnLaserShoot(void); // Generate sound: Laser shoot static void BtnExplosion(void); // Generate sound: Explosion static void BtnPowerup(void); // Generate sound: Powerup static void BtnHitHurt(void); // Generate sound: Hit/Hurt static void BtnJump(void); // Generate sound: Jump static void BtnBlipSelect(void); // Generate sound: Blip/Select static void BtnRandomize(void); // Generate random sound static void BtnMutate(void);; // Mutate current sound static void BtnPlaySound(void); // Play current sound static void BtnLoadSound(void); // Load sound parameters file static void BtnSaveSound(void); // Save sound parameters file static void BtnExportWav(void); // Export current sound as .wav //------------------------------------------------------------------------------------ // Program main entry point //------------------------------------------------------------------------------------ int main(int argc, char *argv[]) { // Command line utility to generate wav files directly from .sfs and .rfx // NOTE: .sfs uses defaults: sampleRate = 22050, sampleSize = 8, channels = 1; // .rfx files contain sampleRate, sampleSize and channels information if (argc > 1) { for (int i = 1; i < argc; i++) { if ((strcmp(GetExtension(argv[i]), "rfx") == 0) || (strcmp(GetExtension(argv[i]), "sfs") == 0)) { LoadSoundParams(argv[i]); GenerateWave(params); argv[i][strlen(argv[i]) - 3] = 'w'; argv[i][strlen(argv[i]) - 2] = 'a'; argv[i][strlen(argv[i]) - 1] = 'v'; //printf("output name: %s\n", argv[i]); SaveWAV(argv[i], wave); } } return 0; } // Initialization //---------------------------------------------------------------------------------------- int screenWidth = 500; int screenHeight = 500; //SetConfigFlags(FLAG_MSAA_4X_HINT); InitWindow(screenWidth, screenHeight, "rFXGen - raylib FX Sound Generator"); InitAudioDevice(); Rectangle paramsRec = { 117, 43, 265, 373 }; Image mask; mask.width = 16; mask.height = 16; mask.mipmaps = 1; mask.format = UNCOMPRESSED_GRAYSCALE; mask.data = imTwitter; Color *pixels = (Color *)malloc(16*16*sizeof(Color)); for (int i = 0; i < 16*16; i++) pixels[i] = WHITE; Image twitter = LoadImageEx(pixels, 16, 16); free(pixels); ImageAlphaMask(&twitter, mask); // Add alpha mask to image ImageFormat(&twitter, UNCOMPRESSED_GRAY_ALPHA); Texture2D texTwitter = LoadTextureFromImage(twitter); UnloadImage(twitter); // Label data //---------------------------------------------------------------------------------------- Rectangle lblAttackTimeRec = { paramsRec.x + 33, paramsRec.y + 5, 100, 10 }; Rectangle lblSustainTimeRec = { paramsRec.x + 31, paramsRec.y + 20, 100, 10 }; Rectangle lblSustainPunchRec = { paramsRec.x + 27, paramsRec.y + 35, 100, 10 }; Rectangle lblDecayTimeRec = { paramsRec.x + 37, paramsRec.y + 50, 100, 10 }; Rectangle lblStartFrequencyRec = { paramsRec.x + 7, paramsRec.y + 66 + 5, 100, 10 }; Rectangle lblMinFrequencyRec = { paramsRec.x + 27, paramsRec.y + 66 + 20, 100, 10 }; Rectangle lblSlideRec = { paramsRec.x + 55, paramsRec.y + 66 + 35, 100, 10 }; Rectangle lblDeltaSlideRec = { paramsRec.x + 35, paramsRec.y + 66 + 50, 100, 10 }; Rectangle lblVibratoDepthRec = { paramsRec.x + 26, paramsRec.y + 66 + 65, 100, 10 }; Rectangle lblVibratoSpeedRec = { paramsRec.x + 27, paramsRec.y + 66 + 80, 100, 10 }; Rectangle lblChangeAmountRec = { paramsRec.x + 25, paramsRec.y + 162 + 5, 100, 10 }; Rectangle lblChangeSpeedRec = { paramsRec.x + 30, paramsRec.y + 162 + 20, 100, 10 }; Rectangle lblSquareDutyRec = { paramsRec.x + 33, paramsRec.y + 198 + 5, 100, 10 }; Rectangle lblDutySweepRec = { paramsRec.x + 36, paramsRec.y + 198 + 20, 100, 10 }; Rectangle lblRepeatSpeedRec = { paramsRec.x + 29, paramsRec.y + 234 + 5, 100, 10 }; Rectangle lblPhaserOffsetRec = { paramsRec.x + 26, paramsRec.y + 255 + 5, 100, 10 }; Rectangle lblPhaserSweepRec = { paramsRec.x + 29, paramsRec.y + 255 + 20, 100, 10 }; Rectangle lblLpfCutoffRec = { paramsRec.x + 37, paramsRec.y + 291 + 5, 100, 10 }; Rectangle lblLpfCutoffSweepRec = { paramsRec.x + 4, paramsRec.y + 291 + 20, 100, 10 }; Rectangle lblLpfResonanceRec = { paramsRec.x + 27, paramsRec.y + 291 + 35, 100, 10 }; Rectangle lblHpfCutoffRec = { paramsRec.x + 36, paramsRec.y + 291 + 50, 100, 10 }; Rectangle lblHpfCutoffSweepRec = { paramsRec.x + 3, paramsRec.y + 291 + 65, 100, 10 }; //---------------------------------------------------------------------------------------- // SliderBar data //---------------------------------------------------------------------------------------- Rectangle sldrAttackTimeRec = { paramsRec.x + 127, paramsRec.y + 5, 100, 10 }; Rectangle sldrSustainTimeRec = { paramsRec.x + 127, paramsRec.y + 20, 100, 10 }; Rectangle sldrSustainPunchRec = { paramsRec.x + 127, paramsRec.y + 35, 100, 10 }; Rectangle sldrDecayTimeRec = { paramsRec.x + 127, paramsRec.y + 50, 100, 10 }; Rectangle sldrStartFrequencyRec = { paramsRec.x + 127, paramsRec.y + 66 + 5, 100, 10 }; Rectangle sldrMinFrequencyRec = { paramsRec.x + 127, paramsRec.y + 66 + 20, 100, 10 }; Rectangle sldrSlideRec = { paramsRec.x + 127, paramsRec.y + 66 + 35, 100, 10 }; Rectangle sldrDeltaSlideRec = { paramsRec.x + 127, paramsRec.y + 66 + 50, 100, 10 }; Rectangle sldrVibratoDepthRec = { paramsRec.x + 127, paramsRec.y + 66 + 65, 100, 10 }; Rectangle sldrVibratoSpeedRec = { paramsRec.x + 127, paramsRec.y + 66 + 80, 100, 10 }; Rectangle sldrChangeAmountRec = { paramsRec.x + 127, paramsRec.y + 162 + 5, 100, 10 }; Rectangle sldrChangeSpeedRec = { paramsRec.x + 127, paramsRec.y + 162 + 20, 100, 10 }; Rectangle sldrSquareDutyRec = { paramsRec.x + 127, paramsRec.y + 198 + 5, 100, 10 }; Rectangle sldrDutySweepRec = { paramsRec.x + 127, paramsRec.y + 198 + 20, 100, 10 }; Rectangle sldrRepeatSpeedRec = { paramsRec.x + 127, paramsRec.y + 234 + 5, 100, 10 }; Rectangle sldrPhaserOffsetRec = { paramsRec.x + 127, paramsRec.y + 255 + 5, 100, 10 }; Rectangle sldrPhaserSweepRec = { paramsRec.x + 127, paramsRec.y + 255 + 20, 100, 10 }; Rectangle sldrLpfCutoffRec = { paramsRec.x + 127, paramsRec.y + 291 + 5, 100, 10 }; Rectangle sldrLpfCutoffSweepRec = { paramsRec.x + 127, paramsRec.y + 291 + 20, 100, 10 }; Rectangle sldrLpfResonanceRec = { paramsRec.x + 127, paramsRec.y + 291 + 35, 100, 10 }; Rectangle sldrHpfCutoffRec = { paramsRec.x + 127, paramsRec.y + 291 + 50, 100, 10 }; Rectangle sldrHpfCutoffSweepRec = { paramsRec.x + 127, paramsRec.y + 291 + 65, 100, 10 }; Rectangle sldrVolumeRec = { 394, 65, 92, 10 }; //---------------------------------------------------------------------------------------- // Button data //---------------------------------------------------------------------------------------- Rectangle btnPickupCoinRec = { 13, 48, 92, 20 }; Rectangle btnLaserShootRec = { 13, 73, 92, 20 }; Rectangle btnExplosionRec = { 13, 98, 92, 20 }; Rectangle btnPowerupRec = { 13, 123, 92, 20 }; Rectangle btnHitHurtRec = { 13, 148, 92, 20 }; Rectangle btnJumpRec = { 13, 173, 92, 20 }; Rectangle btnBlipSelectRec = { 13, 198, 92, 20 }; Rectangle btnMutateRec = { 13, 364, 92, 20 }; Rectangle btnRandomizeRec = { 13, 389, 92, 20 }; Rectangle btnPlaySoundRec = { 394, 81, 92, 20 }; Rectangle btnLoadSoundRec = { 394, 283, 92, 20 }; Rectangle btnSaveSoundRec = { 394, 307, 92, 20 }; Rectangle btnExportWavRec = { 394, 389, 92, 20 }; //---------------------------------------------------------------------------------------- // CheckBox data //---------------------------------------------------------------------------------------- Rectangle chkboxPlayOnChangeRec = { 394, 115, 10, 10 }; bool playOnChangeValue = true; //---------------------------------------------------------------------------------------- // ComboBox data //---------------------------------------------------------------------------------------- Rectangle comboxFrequencyRec = { 394, 340, 61, 20 }; Rectangle comboxBitRateRec = { 394, 364, 61, 20 }; char *comboxFrequencyText[2] = { "44100 Hz", "22050 Hz" }; char *comboxBitRateText[2] = { "16 bit", "8 bit" }; int comboxFrequencyValue = 1; int comboxBitRateValue = 1; //---------------------------------------------------------------------------------------- // ToggleGroup data //---------------------------------------------------------------------------------------- Rectangle tgroupWaveTypeRec = { 117, 15, 64, 20 }; char *tgroupWaveTypeText[4] = { "Square", "Sawtooth", "Sinewave", "Noise" }; //---------------------------------------------------------------------------------------- // Wave default parameters wave.sampleRate = 44100; wave.sampleSize = 32; // 32 bit -> float wave.channels = 1; wave.sampleCount = MAX_WAVE_LENGTH*wave.sampleRate*wave.channels; wave.data = (float *)malloc(wave.sampleCount*sizeof(float)); sound = LoadSoundFromWave(wave); SetSoundVolume(sound, volumeValue); ResetParams(); Rectangle waveRec = { 13, 421, 473, 50 }; #define RENDER_WAVE_TO_TEXTURE #if defined(RENDER_WAVE_TO_TEXTURE) // To avoid enabling MSXAAx4, we will render wave to a texture x2 RenderTexture2D waveTarget = LoadRenderTexture(waveRec.width*2, waveRec.height*2); #endif // Get current directory // NOTE: Current working directory could not match current executable directory GetCurrentDir(currentPath, sizeof(currentPath)); currentPath[strlen(currentPath)] = '\\'; currentPath[strlen(currentPath) + 1] = '\0'; // Not really required SetTargetFPS(60); //---------------------------------------------------------------------------------------- // Main game loop while (!WindowShouldClose()) // Detect window close button or ESC key { // Update & Draw //------------------------------------------------------------------------------------ if (IsKeyPressed(KEY_SPACE)) PlaySound(sound); // Check if mouse is moving sliders to generate new wave data when mouse released if ((CheckCollisionPointRec(GetMousePosition(), (Rectangle){ 265, 65, 105, 360 })) && (IsMouseButtonReleased(MOUSE_LEFT_BUTTON))) { if (playOnChangeValue) GeneratePlay(); else GenerateWave(params); } BeginDrawing(); ClearBackground(GuiBackgroundColor()); DrawRectangleLines(paramsRec.x, paramsRec.y - 1, paramsRec.width, paramsRec.height + 1, GuiLinesColor()); DrawLine(paramsRec.x, paramsRec.y + 66, paramsRec.x + paramsRec.width, paramsRec.y + 66, GuiLinesColor()); DrawLine(paramsRec.x, paramsRec.y + 66 + 96, paramsRec.x + paramsRec.width, paramsRec.y + 66 + 96, GuiLinesColor()); DrawLine(paramsRec.x, paramsRec.y + 162 + 36, paramsRec.x + paramsRec.width, paramsRec.y + 162 + 36, GuiLinesColor()); DrawLine(paramsRec.x, paramsRec.y + 198 + 36, paramsRec.x + paramsRec.width, paramsRec.y + 198 + 36, GuiLinesColor()); DrawLine(paramsRec.x, paramsRec.y + 234 + 21, paramsRec.x + paramsRec.width, paramsRec.y + 234 + 21, GuiLinesColor()); DrawLine(paramsRec.x, paramsRec.y + 291, paramsRec.x + paramsRec.width, paramsRec.y + 291, GuiLinesColor()); DrawLine(13, 225, 105, 224, GuiLinesColor()); DrawLine(13, 358, 105, 358, GuiLinesColor()); DrawLine(394, 108, 486, 108, GuiLinesColor()); DrawLine(394, 277, 486, 277, GuiLinesColor()); DrawLine(394, 334, 486, 334, GuiLinesColor()); // Labels //-------------------------------------------------------------------------------- DrawText("rFXGen", 28, 19, 20, DARKGRAY); GuiLabel(lblAttackTimeRec, "ATTACK TIME"); GuiLabel(lblSustainTimeRec, "SUSTAIN TIME"); GuiLabel(lblSustainPunchRec, "SUSTAIN PUNCH"); GuiLabel(lblDecayTimeRec, "DECAY TIME"); // --------- GuiLabel(lblStartFrequencyRec, "START FREQUENCY"); GuiLabel(lblMinFrequencyRec, "MIN FREQUENCY"); GuiLabel(lblSlideRec, "SLIDE"); GuiLabel(lblDeltaSlideRec, "DELTA SLIDE"); GuiLabel(lblVibratoDepthRec, "VIBRATO DEPTH"); GuiLabel(lblVibratoSpeedRec, "VIBRATO SPEED"); // --------- GuiLabel(lblChangeAmountRec, "CHANGE AMOUNT"); GuiLabel(lblChangeSpeedRec, "CHANGE SPEED"); // --------- GuiLabel(lblSquareDutyRec, "SQUARE DUTY"); GuiLabel(lblDutySweepRec, "DUTY SWEEP"); // --------- GuiLabel(lblRepeatSpeedRec, "REPEAT SPEED"); // --------- GuiLabel(lblPhaserOffsetRec, "PHASER OFFSET"); GuiLabel(lblPhaserSweepRec, "PHASER SWEEP"); // --------- GuiLabel(lblLpfCutoffRec, "LPF CUTOFF"); GuiLabel(lblLpfCutoffSweepRec, "LPF CUTOFF SWEEP"); GuiLabel(lblLpfResonanceRec, "LPF RESONANCE"); GuiLabel(lblHpfCutoffRec, "HPF CUTOFF"); GuiLabel(lblHpfCutoffSweepRec, "HPF CUTOFF SWEEP"); //-------------------------------------------------------------------------------- // Sliders //-------------------------------------------------------------------------------- params.attackTimeValue = GuiSliderBar(sldrAttackTimeRec, params.attackTimeValue, 0, 1); params.sustainTimeValue = GuiSliderBar(sldrSustainTimeRec, params.sustainTimeValue, 0, 1); params.sustainPunchValue = GuiSliderBar(sldrSustainPunchRec, params.sustainPunchValue, 0, 1); params.decayTimeValue = GuiSliderBar(sldrDecayTimeRec, params.decayTimeValue, 0, 1); // -------------- params.startFrequencyValue = GuiSliderBar(sldrStartFrequencyRec, params.startFrequencyValue, 0, 1); params.minFrequencyValue = GuiSliderBar(sldrMinFrequencyRec, params.minFrequencyValue, 0, 1); params.slideValue = GuiSliderBar(sldrSlideRec, params.slideValue, -1, 1); params.deltaSlideValue = GuiSliderBar(sldrDeltaSlideRec, params.deltaSlideValue, -1, 1); params.vibratoDepthValue = GuiSliderBar(sldrVibratoDepthRec, params.vibratoDepthValue, 0, 1); params.vibratoSpeedValue = GuiSliderBar(sldrVibratoSpeedRec, params.vibratoSpeedValue, 0, 1); // -------------- params.changeAmountValue = GuiSliderBar(sldrChangeAmountRec, params.changeAmountValue, -1, 1); params.changeSpeedValue = GuiSliderBar(sldrChangeSpeedRec, params.changeSpeedValue, 0, 1); // -------------- params.squareDutyValue = GuiSliderBar(sldrSquareDutyRec, params.squareDutyValue, 0, 1); params.dutySweepValue = GuiSliderBar(sldrDutySweepRec, params.dutySweepValue, -1, 1); // -------------- params.repeatSpeedValue = GuiSliderBar(sldrRepeatSpeedRec, params.repeatSpeedValue, 0, 1); // --------------- params.phaserOffsetValue = GuiSliderBar(sldrPhaserOffsetRec, params.phaserOffsetValue, -1, 1); params.phaserSweepValue = GuiSliderBar(sldrPhaserSweepRec, params.phaserSweepValue, -1, 1); // --------------- params.lpfCutoffValue = GuiSliderBar(sldrLpfCutoffRec, params.lpfCutoffValue, 0, 1); params.lpfCutoffSweepValue = GuiSliderBar(sldrLpfCutoffSweepRec, params.lpfCutoffSweepValue, -1, 1); params.lpfResonanceValue = GuiSliderBar(sldrLpfResonanceRec, params.lpfResonanceValue, 0, 1); params.hpfCutoffValue = GuiSliderBar(sldrHpfCutoffRec, params.hpfCutoffValue, 0, 1); params.hpfCutoffSweepValue = GuiSliderBar(sldrHpfCutoffSweepRec, params.hpfCutoffSweepValue, -1, 1); // ---------------- float previousVolumeValue = volumeValue; volumeValue = GuiSliderBar(sldrVolumeRec, volumeValue, 0, 1); if (volumeValue != previousVolumeValue) SetSoundVolume(sound, volumeValue); //-------------------------------------------------------------------------------- // Slider values //-------------------------------------------------------------------------------- DrawText(FormatText("%.02f", params.attackTimeValue), sldrAttackTimeRec.x + sldrAttackTimeRec.width + 7, sldrAttackTimeRec.y + 1, 10, DARKGRAY); DrawText(FormatText("%.02f", params.sustainTimeValue), sldrSustainTimeRec.x + sldrSustainTimeRec.width + 7, sldrSustainTimeRec.y + 1, 10, DARKGRAY); DrawText(FormatText("%.02f", params.sustainPunchValue), sldrSustainPunchRec.x + sldrSustainPunchRec.width + 7, sldrSustainPunchRec.y + 1, 10, DARKGRAY); DrawText(FormatText("%.02f", params.decayTimeValue), sldrDecayTimeRec.x + sldrDecayTimeRec.width + 7, sldrDecayTimeRec.y + 1, 10, DARKGRAY); // --------------- DrawText(FormatText("%.02f", params.startFrequencyValue), sldrStartFrequencyRec.x + sldrStartFrequencyRec.width + 7, sldrStartFrequencyRec.y + 1, 10, DARKGRAY); DrawText(FormatText("%.02f", params.minFrequencyValue), sldrMinFrequencyRec.x + sldrMinFrequencyRec.width + 7, sldrMinFrequencyRec.y + 1, 10, DARKGRAY); DrawText(FormatText("%.02f", params.slideValue), sldrSlideRec.x + sldrSlideRec.width + 7, sldrSlideRec.y + 1, 10, DARKGRAY); DrawText(FormatText("%.02f", params.deltaSlideValue), sldrDeltaSlideRec.x + sldrDeltaSlideRec.width + 7, sldrDeltaSlideRec.y + 1, 10, DARKGRAY); DrawText(FormatText("%.02f", params.vibratoDepthValue), sldrVibratoDepthRec.x + sldrVibratoDepthRec.width + 7, sldrVibratoDepthRec.y + 1, 10, DARKGRAY); DrawText(FormatText("%.02f", params.vibratoSpeedValue), sldrVibratoSpeedRec.x + sldrVibratoSpeedRec.width + 7, sldrVibratoSpeedRec.y + 1, 10, DARKGRAY); // --------------- DrawText(FormatText("%.02f", params.changeAmountValue), sldrChangeAmountRec.x + sldrChangeAmountRec.width + 7, sldrChangeAmountRec.y + 1, 10, DARKGRAY); DrawText(FormatText("%.02f", params.changeSpeedValue), sldrChangeSpeedRec.x + sldrChangeSpeedRec.width + 7, sldrChangeSpeedRec.y + 1, 10, DARKGRAY); // --------------- DrawText(FormatText("%.02f", params.squareDutyValue), sldrSquareDutyRec.x + sldrSquareDutyRec.width + 7, sldrSquareDutyRec.y + 1, 10, DARKGRAY); DrawText(FormatText("%.02f", params.dutySweepValue), sldrDutySweepRec.x + sldrDutySweepRec.width + 7, sldrDutySweepRec.y + 1, 10, DARKGRAY); // --------------- DrawText(FormatText("%.02f", params.repeatSpeedValue), sldrRepeatSpeedRec.x + sldrRepeatSpeedRec.width + 7, sldrRepeatSpeedRec.y + 1, 10, DARKGRAY); // --------------- DrawText(FormatText("%.02f", params.phaserOffsetValue), sldrPhaserOffsetRec.x + sldrPhaserOffsetRec.width + 7, sldrPhaserOffsetRec.y + 1, 10, DARKGRAY); DrawText(FormatText("%.02f", params.phaserSweepValue), sldrPhaserSweepRec.x + sldrPhaserSweepRec.width + 7, sldrPhaserSweepRec.y + 1, 10, DARKGRAY); // --------------- DrawText(FormatText("%.02f", params.lpfCutoffValue), sldrLpfCutoffRec.x + sldrLpfCutoffRec.width + 7, sldrLpfCutoffRec.y + 1, 10, DARKGRAY); DrawText(FormatText("%.02f", params.lpfCutoffSweepValue), sldrLpfCutoffSweepRec.x + sldrLpfCutoffSweepRec.width + 7, sldrLpfCutoffSweepRec.y + 1, 10, DARKGRAY); DrawText(FormatText("%.02f", params.lpfResonanceValue), sldrLpfResonanceRec.x + sldrLpfResonanceRec.width + 7, sldrLpfResonanceRec.y + 1, 10, DARKGRAY); DrawText(FormatText("%.02f", params.hpfCutoffValue), sldrHpfCutoffRec.x + sldrHpfCutoffRec.width + 7, sldrHpfCutoffRec.y + 1, 10, DARKGRAY); DrawText(FormatText("%.02f", params.hpfCutoffSweepValue), sldrHpfCutoffSweepRec.x + sldrHpfCutoffSweepRec.width + 7, sldrHpfCutoffSweepRec.y + 1, 10, DARKGRAY); //-------------------------------------------------------------------------------- // Buttons //-------------------------------------------------------------------------------- if (GuiButton(btnPickupCoinRec, "Pickup/Coin")) BtnPickupCoin(); if (GuiButton(btnLaserShootRec, "Laser/Shoot")) BtnLaserShoot(); if (GuiButton(btnExplosionRec, "Explosion")) BtnExplosion(); if (GuiButton(btnPowerupRec, "Powerup")) BtnPowerup(); if (GuiButton(btnHitHurtRec, "Hit/Hurt")) BtnHitHurt(); if (GuiButton(btnJumpRec, "Jump")) BtnJump(); if (GuiButton(btnBlipSelectRec, "Blip/Select")) BtnBlipSelect(); if (GuiButton(btnMutateRec, "Mutate")) BtnMutate(); if (GuiButton(btnRandomizeRec, "Randomize")) BtnRandomize(); if (GuiButton(btnPlaySoundRec, "Play Sound")) BtnPlaySound(); if (GuiButton(btnLoadSoundRec, "Load Sound")) BtnLoadSound(); if (GuiButton(btnSaveSoundRec, "Save Sound")) BtnSaveSound(); if (GuiButton(btnExportWavRec, "Export .Wav")) BtnExportWav(); //-------------------------------------------------------------------------------- // CheckBox //-------------------------------------------------------------------------------- playOnChangeValue = GuiCheckBox(chkboxPlayOnChangeRec, " Play on change", playOnChangeValue); //-------------------------------------------------------------------------------- // ComboBox //-------------------------------------------------------------------------------- comboxFrequencyValue = GuiComboBox(comboxFrequencyRec, 2, comboxFrequencyText, comboxFrequencyValue); comboxBitRateValue = GuiComboBox(comboxBitRateRec, 2, comboxBitRateText, comboxBitRateValue); if (comboxFrequencyValue == 0) wavFrequency = 44100; else if (comboxFrequencyValue == 1) wavFrequency = 22050; if (comboxBitRateValue == 0) wavBitrate = 16; else if (comboxBitRateValue == 1) wavBitrate = 8; //-------------------------------------------------------------------------------- // ToggleGroup //-------------------------------------------------------------------------------- int previousWaveTypeValue = params.waveTypeValue; params.waveTypeValue = GuiToggleGroup(tgroupWaveTypeRec, 4, tgroupWaveTypeText, params.waveTypeValue); if (params.waveTypeValue != previousWaveTypeValue) GeneratePlay(); //-------------------------------------------------------------------------------- if (volumeValue < 1.0f) DrawText(FormatText("VOLUME: %02i %%", (int)(volumeValue*100.0f)), 394, 49, 10, DARKGRAY); else DrawText(FormatText("VOLUME: %02i %%", (int)(volumeValue*100.0f)), 394, 49, 10, DARKGRAY); #if defined(RENDER_WAVE_TO_TEXTURE) BeginTextureMode(waveTarget); DrawWave((Rectangle){ 0, 0, waveTarget.texture.width, waveTarget.texture.height }, MAROON); EndTextureMode(); DrawTextureEx(waveTarget.texture, (Vector2){ waveRec.x, waveRec.y }, 0.0f, 0.5f, WHITE); #else DrawWave(waveRec, MAROON); #endif DrawRectangleLines(waveRec.x, waveRec.y, waveRec.width, waveRec.height, GuiLinesColor()); DrawRectangle(waveRec.x, waveRec.y + waveRec.height/2, waveRec.width, 1, LIGHTGRAY); // Draw status bar DrawRectangle(0, screenHeight - 20, screenWidth, 20, Fade(LIGHTGRAY, 0.5f)); DrawLine(0, screenHeight - 20, screenWidth, screenHeight - 20, LIGHTGRAY); DrawText("SOUND INFO:", 28, 486, 10, DARKGRAY); DrawText(FormatText("num samples: %i", wave.sampleCount), 117, 486, 10, DARKGRAY); DrawText(FormatText("| duration: %i ms", wave.sampleCount*1000/(wave.sampleRate*wave.channels)), 234, 486, 10, DARKGRAY); DrawText(FormatText("| Wave size: %i bytes", wave.sampleCount*wavBitrate/8), 355, 486, 10, DARKGRAY); // Adverts //DrawText("based on sfxr by\nTomas Pettersson", 18, 240, 10, GRAY); DrawText("based on sfxr by", 16, 235, 10, GRAY); DrawText("Tomas Pettersson", 13, 248, 10, GRAY); DrawLine(13, 268, 105, 268, GuiLinesColor()); DrawText("www/github.com/\nraysan5/raygui", 18, 280, 10, GRAY); DrawText("www/github.com/\nraysan5/raylib", 18, 318, 10, GRAY); DrawText("powered by", 394, 149, 10, DARKGRAY); DrawRectangle(394, 162, 92, 92, BLACK); DrawRectangle(400, 168, 80, 80, RAYWHITE); DrawText("raylib", 419, 223, 20, BLACK); //DrawText("based on sfxr by", 394, 13, 10, GRAY); //DrawText("Tomas Pettersson", 392, 25, 10, GRAY); DrawText("@raysan5", 421, 21, 10, GRAY); DrawTexture(texTwitter, 400, 18, Fade(BLACK, 0.4f)); //DrawText("@raysan5", 40, 242, 10, GRAY); //DrawTexture(texTwitter, 20, 240, Fade(BLACK, 0.4f)); EndDrawing(); //------------------------------------------------------------------------------------ //if (doPlay) PlaySample(); } // De-Initialization //---------------------------------------------------------------------------------------- UnloadSound(sound); UnloadWave(wave); #if defined(RENDER_WAVE_TO_TEXTURE) UnloadRenderTexture(waveTarget); #endif UnloadTexture(texTwitter); CloseAudioDevice(); CloseWindow(); // Close window and OpenGL context //---------------------------------------------------------------------------------------- return 0; } //-------------------------------------------------------------------------------------------- // Module Functions Definitions (local) //-------------------------------------------------------------------------------------------- static void ResetParams() { // Wave type params.waveTypeValue = 0; // Wave envelope params params.attackTimeValue = 0.0f; params.sustainTimeValue = 0.3f; params.sustainPunchValue = 0.0f; params.decayTimeValue = 0.4f; // Frequency params params.startFrequencyValue = 0.3f; params.minFrequencyValue = 0.0f; params.slideValue = 0.0f; params.deltaSlideValue = 0.0f; params.vibratoDepthValue = 0.0f; params.vibratoSpeedValue = 0.0f; //params.vibratoPhaseDelay = 0.0f; // Tone change params params.changeAmountValue = 0.0f; params.changeSpeedValue = 0.0f; // Square wave params params.squareDutyValue = 0.0f; params.dutySweepValue = 0.0f; // Repeat params params.repeatSpeedValue = 0.0f; // Phaser params params.phaserOffsetValue = 0.0f; params.phaserSweepValue = 0.0f; // Filter params params.lpfCutoffValue = 1.0f; params.lpfCutoffSweepValue = 0.0f; params.lpfResonanceValue = 0.0f; params.hpfCutoffValue = 0.0f; params.hpfCutoffSweepValue = 0.0f; //params.filterOn = false; } // Generates new wave data and updates sound buffer // NOTE: Operates on global variables: sound, wave static void GenerateWave(WaveParams params) { // Configuration parameters for generation // NOTE: Those parameters are calculated from selected values int phase; double fperiod; double fmaxperiod; double fslide; double fdslide; int period; float squareDuty; float squareSlide; int envelopeStage; int envelopeTime; int envelopeLength[3]; float envelopeVolume; float fphase; float fdphase; int iphase; float phaserBuffer[1024]; int ipp; float noiseBuffer[32]; float fltp; float fltdp; float fltw; float fltwd; float fltdmp; float fltphp; float flthp; float flthpd; float vibratoPhase; float vibratoSpeed; float vibratoAmplitude; int repeatTime; int repeatLimit; int arpeggioTime; int arpeggioLimit; double arpeggioModulation; // Reset sample parameters //---------------------------------------------------------------------------------------- phase = 0; fperiod = 100.0/(params.startFrequencyValue*params.startFrequencyValue + 0.001); period = (int)fperiod; fmaxperiod = 100.0/(params.minFrequencyValue*params.minFrequencyValue + 0.001); fslide = 1.0 - pow((double)params.slideValue, 3.0)*0.01; fdslide = -pow((double)params.deltaSlideValue, 3.0)*0.000001; squareDuty = 0.5f - params.squareDutyValue*0.5f; squareSlide = -params.dutySweepValue*0.00005f; if (params.changeAmountValue >= 0.0f) arpeggioModulation = 1.0 - pow((double)params.changeAmountValue, 2.0)*0.9; else arpeggioModulation = 1.0 + pow((double)params.changeAmountValue, 2.0)*10.0; arpeggioTime = 0; arpeggioLimit = (int)(pow(1.0f - params.changeSpeedValue, 2.0f)*20000 + 32); if (params.changeSpeedValue == 1.0f) arpeggioLimit = 0; // Reset filter parameters fltp = 0.0f; fltdp = 0.0f; fltw = pow(params.lpfCutoffValue, 3.0f)*0.1f; fltwd = 1.0f + params.lpfCutoffSweepValue*0.0001f; fltdmp = 5.0f/(1.0f + pow(params.lpfResonanceValue, 2.0f)*20.0f)*(0.01f + fltw); if (fltdmp > 0.8f) fltdmp = 0.8f; fltphp = 0.0f; flthp = pow(params.hpfCutoffValue, 2.0f)*0.1f; flthpd = 1.0 + params.hpfCutoffSweepValue*0.0003f; // Reset vibrato vibratoPhase = 0.0f; vibratoSpeed = pow(params.vibratoSpeedValue, 2.0f)*0.01f; vibratoAmplitude = params.vibratoDepthValue*0.5f; // Reset envelope envelopeVolume = 0.0f; envelopeStage = 0; envelopeTime = 0; envelopeLength[0] = (int)(params.attackTimeValue*params.attackTimeValue*100000.0f); envelopeLength[1] = (int)(params.sustainTimeValue*params.sustainTimeValue*100000.0f); envelopeLength[2] = (int)(params.decayTimeValue*params.decayTimeValue*100000.0f); fphase = pow(params.phaserOffsetValue, 2.0f)*1020.0f; if (params.phaserOffsetValue < 0.0f) fphase = -fphase; fdphase = pow(params.phaserSweepValue, 2.0f)*1.0f; if (params.phaserSweepValue < 0.0f) fdphase = -fdphase; iphase = abs((int)fphase); ipp = 0; for (int i = 0; i < 1024; i++) phaserBuffer[i] = 0.0f; for (int i = 0; i < 32; i++) noiseBuffer[i] = frnd(2.0f) - 1.0f; // TODO: Review frnd() repeatTime = 0; repeatLimit = (int)(pow(1.0f - params.repeatSpeedValue, 2.0f)*20000 + 32); if (params.repeatSpeedValue == 0.0f) repeatLimit = 0; //---------------------------------------------------------------------------------------- bool generatingSample = true; float *buffer = (float *)wave.data; for (int i = 0; i < MAX_WAVE_LENGTH*wave.sampleRate; i++) { if (!generatingSample) { wave.sampleCount = i; break; } // Generate sample using selected parameters //------------------------------------------------------------------------------------ repeatTime++; if (repeatLimit != 0 && repeatTime >= repeatLimit) { // Reset sample parameters (only some of them) repeatTime = 0; fperiod = 100.0/(params.startFrequencyValue*params.startFrequencyValue + 0.001); period = (int)fperiod; fmaxperiod = 100.0/(params.minFrequencyValue*params.minFrequencyValue + 0.001); fslide = 1.0 - pow((double)params.slideValue, 3.0)*0.01; fdslide = -pow((double)params.deltaSlideValue, 3.0)*0.000001; squareDuty = 0.5f - params.squareDutyValue*0.5f; squareSlide = -params.dutySweepValue*0.00005f; if (params.changeAmountValue >= 0.0f) arpeggioModulation = 1.0 - pow((double)params.changeAmountValue, 2.0)*0.9; else arpeggioModulation = 1.0 + pow((double)params.changeAmountValue, 2.0)*10.0; arpeggioTime = 0; arpeggioLimit = (int)(pow(1.0f - params.changeSpeedValue, 2.0f)*20000 + 32); if (params.changeSpeedValue == 1.0f) arpeggioLimit = 0; } // Frequency envelopes/arpeggios arpeggioTime++; if (arpeggioLimit !=0 && arpeggioTime >= arpeggioLimit) { arpeggioLimit = 0; fperiod *= arpeggioModulation; } fslide += fdslide; fperiod *= fslide; if (fperiod > fmaxperiod) { fperiod = fmaxperiod; if (params.minFrequencyValue > 0.0f) generatingSample = false; // TODO: minFrequencyValue shouldn't be here... } float rfperiod = fperiod; if (vibratoAmplitude > 0.0f) { vibratoPhase += vibratoSpeed; rfperiod = fperiod*(1.0 + sinf(vibratoPhase)*vibratoAmplitude); } period = (int)rfperiod; if (period < 8) period=8; squareDuty += squareSlide; if (squareDuty < 0.0f) squareDuty = 0.0f; if (squareDuty > 0.5f) squareDuty = 0.5f; // Volume envelope envelopeTime++; if (envelopeTime>envelopeLength[envelopeStage]) { envelopeTime = 0; envelopeStage++; if (envelopeStage == 3) generatingSample = false; } if (envelopeStage == 0) envelopeVolume = (float)envelopeTime/envelopeLength[0]; if (envelopeStage == 1) envelopeVolume = 1.0f + pow(1.0f - (float)envelopeTime/envelopeLength[1], 1.0f)*2.0f*params.sustainPunchValue; if (envelopeStage == 2) envelopeVolume = 1.0f - (float)envelopeTime/envelopeLength[2]; // Phaser step fphase += fdphase; iphase = abs((int)fphase); if (iphase > 1023) iphase = 1023; if (flthpd != 0.0f) { flthp *= flthpd; if (flthp < 0.00001f) flthp = 0.00001f; if (flthp > 0.1f) flthp = 0.1f; } float ssample = 0.0f; // Supersampling x8 for (int si = 0; si < 8; si++) { float sample = 0.0f; phase++; if (phase >= period) { //phase = 0; phase %= period; if (params.waveTypeValue == 3) for (int i = 0;i < 32; i++) noiseBuffer[i] = frnd(2.0f) - 1.0f; // TODO: Review frnd() } // base waveform float fp = (float)phase/period; switch (params.waveTypeValue) { case 0: // Square wave { if (fp < squareDuty) sample = 0.5f; else sample = -0.5f; } break; case 1: // Sawtooth wave { sample = 1.0f - fp*2; } break; case 2: // Sine wave { sample = sinf(fp*2*PI); } break; case 3: // Noise wave { sample = noiseBuffer[phase*32/period]; } break; default: break; } // LP filter float pp = fltp; fltw *= fltwd; if (fltw < 0.0f) fltw = 0.0f; if (fltw > 0.1f) fltw = 0.1f; if (params.lpfCutoffValue != 1.0f) { fltdp += (sample-fltp)*fltw; fltdp -= fltdp*fltdmp; } else { fltp = sample; fltdp = 0.0f; } fltp += fltdp; // HP filter fltphp += fltp - pp; fltphp -= fltphp*flthp; sample = fltphp; // Phaser phaserBuffer[ipp&1023] = sample; sample += phaserBuffer[(ipp - iphase + 1024) & 1023]; ipp = (ipp + 1) & 1023; // Final accumulation and envelope application ssample += sample*envelopeVolume; } ssample = ssample/8*masterVolume; // TODO: Why masterVolume? ssample *= 2.0f*volumeValue; //------------------------------------------------------------------------------------ // Accumulate samples in the buffer if (wave.data != NULL) { if (ssample > 1.0f) ssample = 1.0f; if (ssample < -1.0f) ssample = -1.0f; *buffer++ = ssample; } } // Update sound buffer with new data UpdateSound(sound, wave.data, wave.sampleCount); } // Generate wave, update sound and play sound static void GeneratePlay(void) { GenerateWave(params); PlaySound(sound); } // Load .rfx (rFXGen) or .sfs (sfxr) sound parameters file static void LoadSoundParams(const char* fileName) { if (strcmp(GetExtension(fileName),"sfs") == 0) { FILE *file = fopen(fileName, "rb"); // Load .sfs sound parameters int version = 0; fread(&version, 1, sizeof(int), file); if ((version == 100) || (version == 101) || (version == 102)) { fread(¶ms.waveTypeValue, 1, sizeof(int), file); volumeValue = 0.5f; if (version == 102) fread(&volumeValue, 1, sizeof(float), file); fread(¶ms.startFrequencyValue, 1, sizeof(float), file); fread(¶ms.minFrequencyValue, 1, sizeof(float), file); fread(¶ms.slideValue, 1, sizeof(float), file); if (version >= 101) fread(¶ms.deltaSlideValue, 1, sizeof(float), file); fread(¶ms.squareDutyValue, 1, sizeof(float), file); fread(¶ms.dutySweepValue, 1, sizeof(float), file); fread(¶ms.vibratoDepthValue, 1, sizeof(float), file); fread(¶ms.vibratoSpeedValue, 1, sizeof(float), file); float vibratoPhaseDelay = 0.0f; fread(&vibratoPhaseDelay, 1, sizeof(float), file); // Not used fread(¶ms.attackTimeValue, 1, sizeof(float), file); fread(¶ms.sustainTimeValue, 1, sizeof(float), file); fread(¶ms.decayTimeValue, 1, sizeof(float), file); fread(¶ms.sustainPunchValue, 1, sizeof(float), file); bool filterOn = false; fread(&filterOn, 1, sizeof(bool), file); // Not used fread(¶ms.lpfResonanceValue, 1, sizeof(float), file); fread(¶ms.lpfCutoffValue, 1, sizeof(float), file); fread(¶ms.lpfCutoffSweepValue, 1, sizeof(float), file); fread(¶ms.hpfCutoffValue, 1, sizeof(float), file); fread(¶ms.hpfCutoffSweepValue, 1, sizeof(float), file); fread(¶ms.phaserOffsetValue, 1, sizeof(float), file); fread(¶ms.phaserSweepValue, 1, sizeof(float), file); fread(¶ms.repeatSpeedValue, 1, sizeof(float), file); if (version >= 101) { fread(¶ms.changeSpeedValue, 1, sizeof(float), file); fread(¶ms.changeAmountValue, 1, sizeof(float), file); } } else printf("SFS file version not supported\n"); fclose(file); } else if (strcmp(GetExtension(fileName),"rfx") == 0) { FILE *rfxFile = fopen(fileName, "rb"); // Load .rfx sound parameters char signature[4]; fread(signature, 4, sizeof(char), rfxFile); if ((signature[0] == 'r') && (signature[0] == 'F') && (signature[0] == 'X') && (signature[0] == ' ')) { int version; fread(&version, 1, sizeof(int), rfxFile); fread(&volumeValue, 1, sizeof(float), rfxFile); fread(&wavBitrate, 1, sizeof(int), rfxFile); fread(&wavFrequency, 1, sizeof(int), rfxFile); int channels; fread(&channels, 1, sizeof(int), rfxFile); // Read wave parameters struct fread(¶ms, 1, sizeof(WaveParams), rfxFile); } fclose(rfxFile); } } // Save .rfx (rFXGen) or .sfs (sfxr) sound parameters file static void SaveSoundParams(const char* fileName) { if (strcmp(GetExtension(fileName),"sfs") == 0) { FILE *sfsFile = fopen(fileName, "wb"); // Save .sfs sound parameters int version = 102; fwrite(&version, 1, sizeof(int), sfsFile); fwrite(¶ms.waveTypeValue, 1, sizeof(int), sfsFile); fwrite(&volumeValue, 1, sizeof(float), sfsFile); fwrite(¶ms.startFrequencyValue, 1, sizeof(float), sfsFile); fwrite(¶ms.minFrequencyValue, 1, sizeof(float), sfsFile); fwrite(¶ms.slideValue, 1, sizeof(float), sfsFile); fwrite(¶ms.deltaSlideValue, 1, sizeof(float), sfsFile); fwrite(¶ms.squareDutyValue, 1, sizeof(float), sfsFile); fwrite(¶ms.dutySweepValue, 1, sizeof(float), sfsFile); fwrite(¶ms.vibratoDepthValue, 1, sizeof(float), sfsFile); fwrite(¶ms.vibratoSpeedValue, 1, sizeof(float), sfsFile); float vibratoPhaseDelay = 0.0f; fwrite(&vibratoPhaseDelay, 1, sizeof(float), sfsFile); // Not used fwrite(¶ms.attackTimeValue, 1, sizeof(float), sfsFile); fwrite(¶ms.sustainTimeValue, 1, sizeof(float), sfsFile); fwrite(¶ms.decayTimeValue, 1, sizeof(float), sfsFile); fwrite(¶ms.sustainPunchValue, 1, sizeof(float), sfsFile); bool filterOn = false; fwrite(&filterOn, 1, sizeof(bool), sfsFile); // Not used fwrite(¶ms.lpfResonanceValue, 1, sizeof(float), sfsFile); fwrite(¶ms.lpfCutoffValue, 1, sizeof(float), sfsFile); fwrite(¶ms.lpfCutoffSweepValue, 1, sizeof(float), sfsFile); fwrite(¶ms.hpfCutoffValue, 1, sizeof(float), sfsFile); fwrite(¶ms.hpfCutoffSweepValue, 1, sizeof(float), sfsFile); fwrite(¶ms.phaserOffsetValue, 1, sizeof(float), sfsFile); fwrite(¶ms.phaserSweepValue, 1, sizeof(float), sfsFile); fwrite(¶ms.repeatSpeedValue, 1, sizeof(float), sfsFile); fwrite(¶ms.changeSpeedValue, 1, sizeof(float), sfsFile); fwrite(¶ms.changeAmountValue, 1, sizeof(float), sfsFile); fclose(sfsFile); } else if (strcmp(GetExtension(fileName),"rfx") == 0) { FILE *rfxFile = fopen(fileName, "wb"); // Save .rfx sound parameters char signature[4] = "rFX "; fwrite(signature, 4, sizeof(char), rfxFile); int version = 100; // File version fwrite(&version, 1, sizeof(int), rfxFile); fwrite(&volumeValue, 1, sizeof(float), rfxFile); fwrite(&wavBitrate, 1, sizeof(int), rfxFile); fwrite(&wavFrequency, 1, sizeof(int), rfxFile); int channels = 1; fwrite(&channels, 1, sizeof(int), rfxFile); fwrite(¶ms, 1, sizeof(WaveParams), rfxFile); fclose(rfxFile); } } // Draw wave data // NOTE: For proper visualization, MSAA x4 is recommended, alternatively // it should be rendered to a bigger texture and then scaled down with // bilinear/trilinear texture filtering static void DrawWave(Rectangle bounds, Color color) { float sample, sampleNext; float currentSample = 0.0f; float sampleIncrement = (float)wave.sampleCount/(float)(bounds.width*2); float sampleScale = (float)bounds.height; for (int i = 1; i < bounds.width*2 - 1; i++) { sample = ((float *)wave.data)[(int)currentSample]*sampleScale; sampleNext = ((float *)wave.data)[(int)(currentSample + sampleIncrement)]*sampleScale; if (sample > bounds.height/2) sample = bounds.height/2; else if (sample < -bounds.height/2) sample = -bounds.height/2; if (sampleNext > bounds.height/2) sampleNext = bounds.height/2; else if (sampleNext < -bounds.height/2) sampleNext = -bounds.height/2; DrawLineV((Vector2){ (float)bounds.x + (float)i/2.0f, (float)(bounds.y + bounds.height/2) + sample }, (Vector2){ (float)bounds.x + (float)i/2.0f, (float)(bounds.y + bounds.height/2) + sampleNext }, color); currentSample += sampleIncrement; } } // Save wave data to WAV file static void SaveWAV(const char *fileName, Wave wave) { // Basic WAV headers structs typedef struct { char chunkID[4]; int chunkSize; char format[4]; } RiffHeader; typedef struct { char subChunkID[4]; int subChunkSize; short audioFormat; short numChannels; int sampleRate; int byteRate; short blockAlign; short bitsPerSample; } WaveFormat; typedef struct { char subChunkID[4]; int subChunkSize; } WaveData; RiffHeader riffHeader; WaveFormat waveFormat; WaveData waveData; // Fill structs with data riffHeader.chunkID[0] = 'R'; riffHeader.chunkID[1] = 'I'; riffHeader.chunkID[2] = 'F'; riffHeader.chunkID[3] = 'F'; riffHeader.chunkSize = 44 - 4 + wave.sampleCount*wave.sampleSize/8; riffHeader.format[0] = 'W'; riffHeader.format[1] = 'A'; riffHeader.format[2] = 'V'; riffHeader.format[3] = 'E'; waveFormat.subChunkID[0] = 'f'; waveFormat.subChunkID[1] = 'm'; waveFormat.subChunkID[2] = 't'; waveFormat.subChunkID[3] = ' '; waveFormat.subChunkSize = 16; waveFormat.audioFormat = 1; waveFormat.numChannels = 1; waveFormat.sampleRate = wave.sampleRate; waveFormat.byteRate = wave.sampleRate*wave.sampleSize/8; waveFormat.blockAlign = wave.sampleSize/8; waveFormat.bitsPerSample = wave.sampleSize; waveData.subChunkID[0] = 'd'; waveData.subChunkID[1] = 'a'; waveData.subChunkID[2] = 't'; waveData.subChunkID[3] = 'a'; waveData.subChunkSize = wave.sampleCount*wave.sampleSize/8; FILE *wavFile = fopen(fileName, "wb"); fwrite(&riffHeader, 1, sizeof(RiffHeader), wavFile); fwrite(&waveFormat, 1, sizeof(WaveFormat), wavFile); fwrite(&waveData, 1, sizeof(WaveFormat), wavFile); fwrite(wave.data, 1, wave.sampleRate*wave.sampleSize/8, wavFile); fclose(wavFile); } //-------------------------------------------------------------------------------------------- // Buttons functions: sound generation //-------------------------------------------------------------------------------------------- // Generate sound: Pickup/Coin static void BtnPickupCoin(void) { ResetParams(); params.startFrequencyValue = 0.4f + frnd(0.5f); params.attackTimeValue = 0.0f; params.sustainTimeValue = frnd(0.1f); params.decayTimeValue = 0.1f + frnd(0.4f); params.sustainPunchValue = 0.3f + frnd(0.3f); if (rnd(1)) { params.changeSpeedValue = 0.5f + frnd(0.2f); params.changeAmountValue = 0.2f + frnd(0.4f); } GeneratePlay(); } // Generate sound: Laser shoot static void BtnLaserShoot(void) { ResetParams(); params.waveTypeValue = rnd(2); if (params.waveTypeValue == 2 && rnd(1)) params.waveTypeValue = rnd(1); params.startFrequencyValue = 0.5f + frnd(0.5f); params.minFrequencyValue = params.startFrequencyValue - 0.2f - frnd(0.6f); if (params.minFrequencyValue < 0.2f) params.minFrequencyValue = 0.2f; params.slideValue = -0.15f - frnd(0.2f); if (rnd(2) == 0) { params.startFrequencyValue = 0.3f + frnd(0.6f); params.minFrequencyValue = frnd(0.1f); params.slideValue = -0.35f - frnd(0.3f); } if (rnd(1)) { params.squareDutyValue = frnd(0.5f); params.dutySweepValue = frnd(0.2f); } else { params.squareDutyValue = 0.4f + frnd(0.5f); params.dutySweepValue = -frnd(0.7f); } params.attackTimeValue = 0.0f; params.sustainTimeValue = 0.1f + frnd(0.2f); params.decayTimeValue = frnd(0.4f); if (rnd(1)) params.sustainPunchValue = frnd(0.3f); if (rnd(2) == 0) { params.phaserOffsetValue = frnd(0.2f); params.phaserSweepValue = -frnd(0.2f); } if (rnd(1)) params.hpfCutoffValue = frnd(0.3f); GeneratePlay(); } // Generate sound: Explosion static void BtnExplosion(void) { ResetParams(); params.waveTypeValue = 3; if (rnd(1)) { params.startFrequencyValue = 0.1f + frnd(0.4f); params.slideValue = -0.1f + frnd(0.4f); } else { params.startFrequencyValue = 0.2f + frnd(0.7f); params.slideValue = -0.2f - frnd(0.2f); } params.startFrequencyValue *= params.startFrequencyValue; if (rnd(4) == 0) params.slideValue = 0.0f; if (rnd(2) == 0) params.repeatSpeedValue = 0.3f + frnd(0.5f); params.attackTimeValue = 0.0f; params.sustainTimeValue = 0.1f + frnd(0.3f); params.decayTimeValue = frnd(0.5f); if (rnd(1) == 0) { params.phaserOffsetValue = -0.3f + frnd(0.9f); params.phaserSweepValue = -frnd(0.3f); } params.sustainPunchValue = 0.2f + frnd(0.6f); if (rnd(1)) { params.vibratoDepthValue = frnd(0.7f); params.vibratoSpeedValue = frnd(0.6f); } if (rnd(2) == 0) { params.changeSpeedValue = 0.6f + frnd(0.3f); params.changeAmountValue = 0.8f - frnd(1.6f); } GeneratePlay(); } // Generate sound: Powerup static void BtnPowerup(void) { ResetParams(); if (rnd(1)) params.waveTypeValue = 1; else params.squareDutyValue = frnd(0.6f); if (rnd(1)) { params.startFrequencyValue = 0.2f + frnd(0.3f); params.slideValue = 0.1f + frnd(0.4f); params.repeatSpeedValue = 0.4f + frnd(0.4f); } else { params.startFrequencyValue = 0.2f + frnd(0.3f); params.slideValue = 0.05f + frnd(0.2f); if (rnd(1)) { params.vibratoDepthValue = frnd(0.7f); params.vibratoSpeedValue = frnd(0.6f); } } params.attackTimeValue = 0.0f; params.sustainTimeValue = frnd(0.4f); params.decayTimeValue = 0.1f + frnd(0.4f); GeneratePlay(); } // Generate sound: Hit/Hurt static void BtnHitHurt(void) { ResetParams(); params.waveTypeValue = rnd(2); if (params.waveTypeValue == 2) params.waveTypeValue = 3; if (params.waveTypeValue == 0) params.squareDutyValue = frnd(0.6f); params.startFrequencyValue = 0.2f + frnd(0.6f); params.slideValue = -0.3f - frnd(0.4f); params.attackTimeValue = 0.0f; params.sustainTimeValue = frnd(0.1f); params.decayTimeValue = 0.1f + frnd(0.2f); if (rnd(1)) params.hpfCutoffValue = frnd(0.3f); GeneratePlay(); } // Generate sound: Jump static void BtnJump(void) { ResetParams(); params.waveTypeValue = 0; params.squareDutyValue = frnd(0.6f); params.startFrequencyValue = 0.3f + frnd(0.3f); params.slideValue = 0.1f + frnd(0.2f); params.attackTimeValue = 0.0f; params.sustainTimeValue = 0.1f + frnd(0.3f); params.decayTimeValue = 0.1f + frnd(0.2f); if (rnd(1)) params.hpfCutoffValue = frnd(0.3f); if (rnd(1)) params.lpfCutoffValue = 1.0f - frnd(0.6f); GeneratePlay(); } // Generate sound: Blip/Select static void BtnBlipSelect(void) { ResetParams(); params.waveTypeValue = rnd(1); if (params.waveTypeValue == 0) params.squareDutyValue = frnd(0.6f); params.startFrequencyValue = 0.2f + frnd(0.4f); params.attackTimeValue = 0.0f; params.sustainTimeValue = 0.1f + frnd(0.1f); params.decayTimeValue = frnd(0.2f); params.hpfCutoffValue = 0.1f; GeneratePlay(); } // Generate random sound static void BtnRandomize(void) { params.startFrequencyValue = pow(frnd(2.0f) - 1.0f, 2.0f); if (rnd(1)) params.startFrequencyValue = pow(frnd(2.0f) - 1.0f, 3.0f)+0.5f; params.minFrequencyValue = 0.0f; params.slideValue = pow(frnd(2.0f) - 1.0f, 5.0f); if ((params.startFrequencyValue > 0.7f) && (params.slideValue > 0.2f)) params.slideValue = -params.slideValue; if ((params.startFrequencyValue < 0.2f) && (params.slideValue < -0.05f)) params.slideValue = -params.slideValue; params.deltaSlideValue = pow(frnd(2.0f) - 1.0f, 3.0f); params.squareDutyValue = frnd(2.0f) - 1.0f; params.dutySweepValue = pow(frnd(2.0f) - 1.0f, 3.0f); params.vibratoDepthValue = pow(frnd(2.0f) - 1.0f, 3.0f); params.vibratoSpeedValue = frnd(2.0f) - 1.0f; //params.vibratoPhaseDelay = frnd(2.0f) - 1.0f; params.attackTimeValue = pow(frnd(2.0f) - 1.0f, 3.0f); params.sustainTimeValue = pow(frnd(2.0f) - 1.0f, 2.0f); params.decayTimeValue = frnd(2.0f)-1.0f; params.sustainPunchValue = pow(frnd(0.8f), 2.0f); if (params.attackTimeValue + params.sustainTimeValue + params.decayTimeValue < 0.2f) { params.sustainTimeValue += 0.2f + frnd(0.3f); params.decayTimeValue += 0.2f + frnd(0.3f); } params.lpfResonanceValue = frnd(2.0f) - 1.0f; params.lpfCutoffValue = 1.0f - pow(frnd(1.0f), 3.0f); params.lpfCutoffSweepValue = pow(frnd(2.0f) - 1.0f, 3.0f); if (params.lpfCutoffValue < 0.1f && params.lpfCutoffSweepValue < -0.05f) params.lpfCutoffSweepValue = -params.lpfCutoffSweepValue; params.hpfCutoffValue = pow(frnd(1.0f), 5.0f); params.hpfCutoffSweepValue = pow(frnd(2.0f) - 1.0f, 5.0f); params.phaserOffsetValue = pow(frnd(2.0f) - 1.0f, 3.0f); params.phaserSweepValue = pow(frnd(2.0f) - 1.0f, 3.0f); params.repeatSpeedValue = frnd(2.0f) - 1.0f; params.changeSpeedValue = frnd(2.0f) - 1.0f; params.changeAmountValue = frnd(2.0f) - 1.0f; GeneratePlay(); } // Mutate current sound static void BtnMutate(void) { if (rnd(1)) params.startFrequencyValue += frnd(0.1f) - 0.05f; //if (rnd(1)) params.minFrequencyValue += frnd(0.1f) - 0.05f; if (rnd(1)) params.slideValue += frnd(0.1f) - 0.05f; if (rnd(1)) params.deltaSlideValue += frnd(0.1f) - 0.05f; if (rnd(1)) params.squareDutyValue += frnd(0.1f) - 0.05f; if (rnd(1)) params.dutySweepValue += frnd(0.1f) - 0.05f; if (rnd(1)) params.vibratoDepthValue += frnd(0.1f) - 0.05f; if (rnd(1)) params.vibratoSpeedValue += frnd(0.1f) - 0.05f; //if (rnd(1)) params.vibratoPhaseDelay += frnd(0.1f) - 0.05f; if (rnd(1)) params.attackTimeValue += frnd(0.1f) - 0.05f; if (rnd(1)) params.sustainTimeValue += frnd(0.1f) - 0.05f; if (rnd(1)) params.decayTimeValue += frnd(0.1f) - 0.05f; if (rnd(1)) params.sustainPunchValue += frnd(0.1f) - 0.05f; if (rnd(1)) params.lpfResonanceValue += frnd(0.1f) - 0.05f; if (rnd(1)) params.lpfCutoffValue += frnd(0.1f) - 0.05f; if (rnd(1)) params.lpfCutoffSweepValue += frnd(0.1f) - 0.05f; if (rnd(1)) params.hpfCutoffValue += frnd(0.1f) - 0.05f; if (rnd(1)) params.hpfCutoffSweepValue += frnd(0.1f) - 0.05f; if (rnd(1)) params.phaserOffsetValue += frnd(0.1f) - 0.05f; if (rnd(1)) params.phaserSweepValue += frnd(0.1f) - 0.05f; if (rnd(1)) params.repeatSpeedValue += frnd(0.1f) - 0.05f; if (rnd(1)) params.changeSpeedValue += frnd(0.1f) - 0.05f; if (rnd(1)) params.changeAmountValue += frnd(0.1f) - 0.05f; GeneratePlay(); } //-------------------------------------------------------------------------------------------- // Buttons functions: sound playing and export functions //-------------------------------------------------------------------------------------------- // Play current sound static void BtnPlaySound(void) { PlaySound(sound); } // Load sound parameters file static void BtnLoadSound(void) { // Open file dialog const char *filters[] = { "*.rfx", "*.sfs" }; const char *fileName = tinyfd_openFileDialog("Load sound parameters file", currentPath, 2, filters, "Sound Param Files (*.rfx, *.sfs)", 0); if (fileName != NULL) { LoadSoundParams(fileName); //GeneratePlay(); } } // Save sound parameters file static void BtnSaveSound(void) { char currrentPathFile[256]; // Add sample file name to currentPath strcpy(currrentPathFile, currentPath); strcat(currrentPathFile, "sound.rfx\0"); // Save file dialog const char *filters[] = { "*.rfx", "*.sfs" }; const char *fileName = tinyfd_saveFileDialog("Save sound parameters file", currrentPathFile, 2, filters, "Sound Param Files (*.rfx, *.sfs)"); if (fileName != NULL) SaveSoundParams(fileName); } // Export current sound as .wav static void BtnExportWav(void) { char currrentPathFile[256]; // Add sample file name to currentPath strcpy(currrentPathFile, currentPath); strcat(currrentPathFile, "sound.wav\0"); // Save file dialog const char *filters[] = { "*.wav" }; const char *fileName = tinyfd_saveFileDialog("Save wave file", currrentPathFile, 1, filters, "Wave File (*.wav)"); Wave ewave = WaveCopy(wave); WaveFormat(&ewave, wavFrequency, wavBitrate, 1); SaveWAV(fileName, ewave); UnloadWave(ewave); } //-------------------------------------------------------------------------------------------- // Helper functions //-------------------------------------------------------------------------------------------- // Get the extension for a filename static const char *GetExtension(const char *fileName) { const char *dot = strrchr(fileName, '.'); if (!dot || dot == fileName) return ""; return (dot + 1); }