Генерация звуков на C

Требуется сгенерировать звук определённой частоты и продолжительности, но без спикера, а через вывод звука. Что-то наподобие такого:

Beep(350, 500);

Есть ли такие библиотеки под C? Или в каком направлении смотреть?


Ответы (2 шт):

Автор решения: Arty

Интересная у вас задача. Только что решил написать удобную небольшую библиотеку-обёртку, использующую мультимедийную кроссплатформенную библиотеку SDL (написанную на C).

Получилась обёртка, которая может исполняться на любой популярной ОС (Windows, Linux, MacOS, Android, iOS).

Также можете посмотреть мою аналогичную библиотеку-обёртку проигрывания WAV также используя SDL.

Библиотека содержит 3 файла - play_sine.h заголовочный файл, play_sine.c файл реализующий функции, play_sine_usage_example.c пример использования библиотеки.

Библиотека кроме функции проигрывания содержит дополнительный функционал и макросы для сборки и вывода возможных ошибок, что занимает значительную часть библиотеки. Если интересна только функция проигрывания звука то смотрите только PlaySine(...) в play_sine.c.

Использовать очень просто - достаточно вызвать для примера PlaySine(500, 5000) аналогично Beep(500, 5000) и возможно проверить ошибку как в примере. Также можно выставить уровень громкости до этого, но не обязательно, через PlaySineSetVolume(0.3) (на 30%), по умолчанию уровень громкости в районе 80%.

Скомпилировать код можно следующим образом:

Под Windows (используя MSVC):

  1. Скачиваем SDL, например последнюю версию на сегодня.

  2. Помещаем содержимое (файлы) из ./include/ (в архиве) в папку SDL2/. Папку ./lib/ из архива просто копируем.

  3. Запускаем MSVC консоль.

  4. Компилируем: cl play_sine_usage_example.c play_sine.c ./lib/x64/SDL2.lib -I.. Здесь x64 для 64-битного приложения и x86 для 32-битного.

  5. Приложение EXE готово.

  6. Из ./lib/ папки рядом с EXE кладём соответствующую DLL. Если наличие DLL не удобно (меня например это не устраивает в моих проектах), то можно скачав SDL исходники скомпилировать их в статическую .lib.

  7. Запускаем play_sine_usage_example.exe.

Под Linux (используя GCC):

  1. Установить SDL через sudo apt install libsdl2-dev. Также альтернативно можно скачать как для Windows исходники с сайта SDL и скомпилировать статическую версию.

  2. Скомпилировать код моего примера и библиотеки через gcc -o play_sine_usage_example play_sine_usage_example.c play_sine.c -lm -lSDL2.

  3. Запустить ./play_sine_usage_example.

  4. Пример компиляции и запуска под Linux на удалённом сервере можно найти тут (конечно удалённый сервер звук не проиграет, но программа соберётся и запустится).

play_sine_usage_example.c:

// play_sine_usage_example.c

#include "play_sine.h"
#include <stdio.h>

int main() {
    if (PlaySineSetVolume(0.3) != 0)
        printf("Error:\n%s\n", PlaySineError(1));
    if (PlaySine(500, 5000) != 0)
        printf("Error:\n%s\n", PlaySineError(1));
}

play_sine.h:

// play_sine.h

#ifndef _PLAY_WAV_H_
#define _PLAY_WAV_H_

#include <stdint.h>
// Play Sine Wave.
// Returns 0 on success, and non-0 on error.
uint32_t PlaySine(uint32_t freq, uint32_t duration_ms);
// Set volume, from 0.0 to 1.0.
// Returns 0 on success, and non-0 on error.
uint32_t PlaySineSetVolume(double vol);
// Returns error stack, clears errors stack if clear_errors != 0.
char const * PlaySineError(int clear_errors);

#endif // _PLAY_WAV_H_

play_sine.c:

// play_sine.c

#include "play_sine.h"

#include <SDL2/SDL.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <math.h>

enum PlayWavErrors {
    ERR_PLAY_SINE_SDL_FAILED = 0xC8B30001,
    ERR_PLAY_SINE_WRONG_VOLUME,
    ERR_PLAY_SINE_WRONG_FREQ,
    ERR_PLAY_SINE_MALLOC_FAILED,
};

#define MEMZERO(obj) memset(&(obj), 0, sizeof(obj))
#define MAX(a, b) ((a) >= (b) ? (a) : (b))
#define MIN(a, b) ((a) <= (b) ? (a) : (b))
#ifndef M_PI
    #define M_PI 3.14159265358979
#endif

typedef uint32_t Error; 
typedef int16_t Int16; 

struct ErrInfo {
    uint32_t line;
    Error err;
    char const * serr;
    char const * func;
    char const * code;
    char const * file;
    char msg[256];
};

enum { c_max_errors_cnt = 128 };

static struct ErrInfo g_errors[c_max_errors_cnt];
static uint32_t g_errors_cnt = 0;

static void ClearErrors() {
    MEMZERO(g_errors);
    g_errors_cnt = 0;
}

#define ERRIFMSGFIN(cond, err_, msg_, fin_) \
    if (cond) { \
        if (g_errors_cnt < c_max_errors_cnt) { \
            g_errors[g_errors_cnt].err = (err_); \
            g_errors[g_errors_cnt].line = __LINE__; \
            g_errors[g_errors_cnt].func = __FUNCTION__; \
            g_errors[g_errors_cnt].file = __FILE__; \
            g_errors[g_errors_cnt].code = #cond; \
            g_errors[g_errors_cnt].serr = #err_; \
            MEMZERO(g_errors[g_errors_cnt].msg); \
            if (msg_) strncpy(g_errors[g_errors_cnt].msg, (msg_), sizeof(g_errors[g_errors_cnt].msg) - 1); \
            ++g_errors_cnt; \
        } \
        fin_; \
        return (err_); \
    }
#define ERRIFMSG(cond, err_, msg_) ERRIFMSGFIN(cond, err_, msg_, {})
#define ERRIF(cond, err) ERRIFMSG(cond, err, "")
#define SDLERRIF(cond) ERRIFMSG(cond, ERR_PLAY_SINE_SDL_FAILED, SDL_GetError())
#define SDLERRIFFIN(cond, fin) ERRIFMSGFIN(cond, ERR_PLAY_SINE_SDL_FAILED, SDL_GetError(), fin)

static bool g_sdl_inited = false;

static void SdlInit() {
    if (!g_sdl_inited) {
        SDL_Init(SDL_INIT_AUDIO);
        g_sdl_inited = true;
    }
}

static void SdlQuit() {
    if (g_sdl_inited) {
        SDL_Quit();
        g_sdl_inited = false;
    }
}

static uint32_t g_amplitude = 28000;

Error PlaySineSetVolume(double vol) {
    ERRIFMSG((vol < 0 || vol > 1), ERR_PLAY_SINE_WRONG_VOLUME, "PlaySine: Wrong volume!");
    g_amplitude = (uint32_t)(vol * 32766);
    return 0;
}

Error PlaySine(uint32_t freq, uint32_t duration_ms) {
    ERRIFMSG(freq > 44100, ERR_PLAY_SINE_WRONG_FREQ, "PlaySine: Wrong frequency!");
    
    SdlInit();
    
    uint32_t const base_freq = freq <= 4000 ? 8000 : freq <= 8000 ? 16000 : freq <= 11025 ? 22050 : 44100;
    enum { samples_block = (1 << 17), samples_dev = 4096 };

    // Set audio spec.    
    SDL_AudioSpec spec; MEMZERO(spec);
    spec.freq = base_freq;
    spec.format = AUDIO_S16SYS;
    spec.channels = 1;
    spec.samples = samples_dev;
    
    // Open audio device.
    SDL_AudioDeviceID dev = 0;
    SDLERRIF((dev = SDL_OpenAudioDevice(NULL, 0, &spec, NULL, 0)) == 0);

    // Gen sine wave.    
    uint32_t const nsamples = base_freq * duration_ms / 1000;
    Int16 * samples = (Int16*)malloc(samples_block * sizeof(Int16));
    ERRIFMSG(samples == 0, ERR_PLAY_SINE_MALLOC_FAILED, "PlaySine: Mem alloc failed!");
    for (uint32_t i = 0; i < samples_block; ++i)
        samples[i] = (Int16)(((double)g_amplitude) * sin(((double)freq) * i / base_freq * 2 * ((double)(M_PI))));

    // stop if audio is playing    
    SDL_PauseAudioDevice(dev, 1);
    // clear existing audio in queue
    SDL_ClearQueuedAudio(dev);
    
    // play audio
    bool finished = false;
    for (uint32_t nplayed = 0;; nplayed += samples_block) {
        SDLERRIFFIN(SDL_QueueAudio(dev, samples, samples_block * sizeof(Int16)) != 0, {
            free(samples);
        });
        if (nplayed == 0) {
            // start playing audio
            SDL_PauseAudioDevice(dev, 0);
        }
        while (1) {
            Uint32 queued_size = SDL_GetQueuedAudioSize(dev);
            if (nplayed + samples_block - queued_size / sizeof(Int16) >= nsamples) {
                finished = true;
                break;
            }
            if (queued_size <= (samples_dev * 3) * sizeof(Int16))
                break;
            SDL_Delay(10);
        }
        if (finished)
            break;
    }
    
    free(samples);

    // stop playing
    SDL_PauseAudioDevice(dev, 1);
    // clear existing audio in queue
    SDL_ClearQueuedAudio(dev);
    
    // clean up
    SDL_CloseAudioDevice(dev);
     
    return 0;
}

char const * PlaySineError(int clear_errors) {
    static char buf[2048];
    MEMZERO(buf);
    for (uint32_t i = 0; i < g_errors_cnt; ++i)
        snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "%d:%s:%d:%s():\"%s\":\"%s\"\n",
            i, g_errors[i].file, g_errors[i].line, g_errors[i].func, /*g_errors[i].serr, g_errors[i].err,*/ g_errors[i].code, g_errors[i].msg);
    if (clear_errors)
        ClearErrors();
    return buf;
}
→ Ссылка
Автор решения: user622197

Нашёл в интернете код на С++ решающий проблему топикстартера. Переделал его на Си, в Pelles C компилируется, работает.

#include <windows.h>
#include <math.h>

// подключаем LIB-файл для ф-ции PlaySound
#pragma comment(lib, "winmm.lib")

// описание заголовка файла WAV
typedef struct WAVHEADER
{
    char    sigRIFF[4];     // должно быть равно "RIFF"
    DWORD   sizeRIFFch;     // размер чанка RIFF
    char    sigWAVE[4];     // должно быть равно "WAVE"
    char    sigFMT[4];      // должно быть равно "fmt "
    DWORD   sizeFMTch;      // размер чанка FMT
    WORD    wFormatTag;     // категория формата, для PCM = 1
    WORD    wChannels;      // кол-во каналов: 1-моно 2-стерео
    DWORD   dwSamplesPerSec;// кол-во сэмплов в сек.
    DWORD   dwAvgBytesPerSec;// среднее число байт в сек
    WORD    wBlockAlign;    // выравнивание данных в дата-чанке
    WORD    wBitPerSample;  // бит в сэмпле
    char    sigDATA[4];     // должно быть равно "data"
    DWORD   sizeDATAch;     // размер data-чанка
} WAVHEADER;


void SuperBeep(DWORD dwFrequency, DWORD dwMilliSeconds)
{
    // частота дискретизации = 44100 Гц
    // кол-во бит на сэмпл = 8
    // если требуемая частота > 44100 или равна нулю, то выходим
    if(!dwFrequency || dwFrequency>44100)
        return;

    // длина дорожки в байтах
    DWORD numSamples = 44100 / dwFrequency;
   
    // выделяем память под дорожку
    DWORD size = sizeof(WAVHEADER) + numSamples;
   void *buff = (char*)malloc(size);

    // заполняем WAV-header
    WAVHEADER *head = (WAVHEADER*)buff;
    strcpy(head->sigRIFF, "RIFF");
    strcpy(head->sigWAVE, "WAVE");
    head->sizeRIFFch = size - 8;
    strcpy(head->sigFMT, "fmt ");
    head->sizeFMTch = 16;
    head->wFormatTag = 1;
    head->wChannels = 1; // моно
    head->dwSamplesPerSec = 44100;
    head->dwAvgBytesPerSec = 44100;
    head->wBlockAlign = 1;
    head->wBitPerSample = 8;
    strcpy(head->sigDATA, "data");
    head->sizeDATAch = size;

    // заполняем дорожку периодом синуса
    BYTE *samples = (BYTE*)(head+1);
    for(DWORD i=0; i<numSamples; i++)
        samples[i] = (BYTE)(255*sin(6.28*(double)i/(double)numSamples));

    // проигрываем звук
    PlaySound((const char*)buff, 0, SND_ASYNC|SND_LOOP|SND_MEMORY);

    // ждём заданное количество миллисекунд
    Sleep(dwMilliSeconds);

    // останавливаем звук
    PlaySound(0, 0, SND_ASYNC);
    free(buff);
}


int main(int argc, char* argv[])
{
    SuperBeep(20000, 2000);
    SuperBeep(2000, 2000);
    SuperBeep(200, 2000);
    SuperBeep(100, 2000);
    SuperBeep(20, 2000);
    return 0;
}
→ Ссылка