LinuxQuestions.org
Review your favorite Linux distribution.
Go Back   LinuxQuestions.org > Forums > Non-*NIX Forums > Programming
User Name
Password
Programming This forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.

Notices


Reply
  Search this Thread
Old 10-13-2021, 01:22 AM   #1
mahaju
Member
 
Registered: Nov 2011
Posts: 39

Rep: Reputation: Disabled
Generating puretone sinewave in raspberry pi zero using C++ and ALSA api


I am trying to figure out how to generate 1 kHz tone in raspberry pi zero W board using ALSA api and C++, but I don't seem to be getting anywhere. Strangely there seems to be no beginner friendly step-by-step tutorial on generating sound using ALSA, when I thought it was the most common way of generating sound in linux.

I am using a pi zero W board with raspberry pi OS. I have a USB sound card connected to a USB hub, which is connected to the USB port on the pi zero board. I can play wav files from both VLC and command line using aplay, so there is no problem with the hardware. I can also run the small test program given here: https://forums.raspberrypi.com/viewtopic.php?t=15075, so I don't think there is any problem with my ALSA installation either.

Attempt 1:
I have tried running the test/pcm.c sample code given in ALSA's website: https://www.alsa-project.org/alsa-do...c-example.html
I compile this as
Code:
gcc main.cpp -o Main -lasound -Wall -fpermissive
I get a couple of warnings and then an error saying no match for operator++ for snd_pcm_format_t (which is an enum type). the error is specifically at the line
Code:
for (format = 0; format < SND_PCM_FORMAT_LAST; format++) {
(under case 'o' in the part that parses the command line arguments). I am stuck after this and not sure what to do.

Attempt 2:
I am trying out the program given here: http://equalarea.com/paul/alsa-audio.html, under "A Minimal Interrupt-Driven Program". This program compiles after some modifications, and I am manually calculating 1kHz sinewave samples at 48000 Hz sampling frequency. Problem is the generated tone doesn't sound anything like 1 kHz pure tone. I have a hunch this distortion is because the output is saturated, since it is quite loud, but I need some confirmation that this is the case. I have tried to simply reduce the amplitude of the sine function by multiplying it with 0.1, but that still generates the same distorted tone sound, only a lot smaller. I can't find the proper way to reduce the volume in ALSA api either. This is the code I am testing:

Code:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <poll.h>
#include <math.h>
#include <alsa/asoundlib.h>
      
snd_pcm_t *playback_handle;
// short buf[4096];
short buf[48*2];        // try 1000 Hz @48000, 48 samples @48000 = 1 ms

void sine_gen(){
    // call once to generate the sine values
    // if this works do this inside callback function later on
    double tempSin = 0.0;
    #if 1
    // check if buf[] contains 2 channels interleaved
    for(int i = 0; i<48*2; i+=2){
        tempSin = sin(2.0*M_PI*1000*i/48000);
        buf[i] = (short)(tempSin * (pow(2.0, 15)- 1);)
        buf[i+1] = buf[i];
    }
    #else
    // check if buf[] contains single channel data
    for(int i = 0; i<48*2; i++){
        tempSin = sin(2*M_PI*1000*i/48000);
        buf[i] = (short)(tempSin * (pow(2.0, 15)-1) );
    }   
    #endif
    
}

void SetAlsaMasterVolume(long volume)
{
    long min, max;
    snd_mixer_t *handle;
    snd_mixer_selem_id_t *sid;
    const char *card = "default";
    const char *selem_name = "Master";

    snd_mixer_open(&handle, 0);
    snd_mixer_attach(handle, card);
    snd_mixer_selem_register(handle, NULL, NULL);
    snd_mixer_load(handle);

    snd_mixer_selem_id_alloca(&sid);
    snd_mixer_selem_id_set_index(sid, 0);
    snd_mixer_selem_id_set_name(sid, selem_name);
    snd_mixer_elem_t* elem = snd_mixer_find_selem(handle, sid);

    snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
    snd_mixer_selem_set_playback_volume_all(elem, volume * max / 100);

    snd_mixer_close(handle);
}

int
playback_callback (snd_pcm_sframes_t nframes)
{
    int err;

    // printf ("playback callback called with %u frames\n", nframes);

    /* ... fill buf with data ... */
    // if this part is empty, buf[] should have been filled correctly inside sine_gen()
    
    if ((err = snd_pcm_writei (playback_handle, buf, nframes)) < 0) {
        fprintf (stderr, "write failed (%s)\n", snd_strerror (err));
    }
    
    return err;
}
      
int main (int argc, char *argv[])
{

    snd_pcm_hw_params_t *hw_params;
    snd_pcm_sw_params_t *sw_params;
    snd_pcm_sframes_t frames_to_deliver;
    int nfds;
    int err;
    struct pollfd *pfds;
    
    sine_gen();     // call this once to generate sinewave

    if ((err = snd_pcm_open (&playback_handle, argv[1], SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
        fprintf (stderr, "cannot open audio device %s (%s)\n", 
             argv[1],
             snd_strerror (err));
        exit (1);
    }
    
    if ((err = snd_pcm_hw_params_malloc (&hw_params)) < 0) {
        fprintf (stderr, "cannot allocate hardware parameter structure (%s)\n",
             snd_strerror (err));
        exit (1);
    }
    
    if ((err = snd_pcm_hw_params_any (playback_handle, hw_params)) < 0) {
        fprintf (stderr, "cannot initialize hardware parameter structure (%s)\n",
             snd_strerror (err));
        exit (1);
    }
    
    if ((err = snd_pcm_hw_params_set_access (playback_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
        fprintf (stderr, "cannot set access type (%s)\n",
             snd_strerror (err));
        exit (1);
    }
    
    if ((err = snd_pcm_hw_params_set_format (playback_handle, hw_params, SND_PCM_FORMAT_S16_LE)) < 0) {
        fprintf (stderr, "cannot set sample format (%s)\n",
             snd_strerror (err));
        exit (1);
    }
    
    unsigned int f_s = 48000;
    // if ((err = snd_pcm_hw_params_set_rate_near (playback_handle, hw_params, 44100, 0)) < 0) {        // causes segmentation fault; see // https://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m___h_w___params.html#ga6014e0e1ec7934f8c745290e83e59199
    if ((err = snd_pcm_hw_params_set_rate_near (playback_handle, hw_params, &f_s, 0)) < 0) {
        fprintf (stderr, "cannot set sample rate (%s)\n",
             snd_strerror (err));
        exit (1);
    }
    
    if ((err = snd_pcm_hw_params_set_channels (playback_handle, hw_params, 2)) < 0) {
        fprintf (stderr, "cannot set channel count (%s)\n",
             snd_strerror (err));
        exit (1);
    }
    
    if ((err = snd_pcm_hw_params (playback_handle, hw_params)) < 0) {
        fprintf (stderr, "cannot set parameters (%s)\n",
             snd_strerror (err));
        exit (1);
    }
    
    snd_pcm_hw_params_free (hw_params);
    
    /* tell ALSA to wake us up whenever 4096 or more frames
       of playback data can be delivered. Also, tell
       ALSA that we'll start the device ourselves.
    */

    if ((err = snd_pcm_sw_params_malloc (&sw_params)) < 0) {
        fprintf (stderr, "cannot allocate software parameters structure (%s)\n",
             snd_strerror (err));
        exit (1);
    }
    if ((err = snd_pcm_sw_params_current (playback_handle, sw_params)) < 0) {
        fprintf (stderr, "cannot initialize software parameters structure (%s)\n",
             snd_strerror (err));
        exit (1);
    }
    // if ((err = snd_pcm_sw_params_set_avail_min (playback_handle, sw_params, 4096)) < 0) {        // change this as per the size of the buffer used
    if ((err = snd_pcm_sw_params_set_avail_min (playback_handle, sw_params, 48*2)) < 0) {       
        fprintf (stderr, "cannot set minimum available count (%s)\n",
             snd_strerror (err));
        exit (1);
    }
    if ((err = snd_pcm_sw_params_set_start_threshold (playback_handle, sw_params, 0U)) < 0) {
        fprintf (stderr, "cannot set start mode (%s)\n",
             snd_strerror (err));
        exit (1);
    }
    if ((err = snd_pcm_sw_params (playback_handle, sw_params)) < 0) {
        fprintf (stderr, "cannot set software parameters (%s)\n",
             snd_strerror (err));
        exit (1);
    }

    /* the interface will interrupt the kernel every 4096 frames, and ALSA
       will wake up this program very soon after that.
    */

    if ((err = snd_pcm_prepare (playback_handle)) < 0) {
        fprintf (stderr, "cannot prepare audio interface for use (%s)\n",
             snd_strerror (err));
        exit (1);
    }
    
    // SetAlsaMasterVolume(0);      // attempt changing volume      // didn't make any difference

    while (1) {

        /* wait till the interface is ready for data, or 1 second
           has elapsed.
        */

        if ((err = snd_pcm_wait (playback_handle, 1000)) < 0) {
                fprintf (stderr, "poll failed (%s)\n", strerror (errno));
                break;
        }              

        /* find out how much space is available for playback data */

        if ((frames_to_deliver = snd_pcm_avail_update (playback_handle)) < 0) {
            if (frames_to_deliver == -EPIPE) {
                fprintf (stderr, "an xrun occured\n");
                break;
            } else {
                fprintf (stderr, "unknown ALSA avail update return value (%d)\n", 
                     frames_to_deliver);
                break;
            }
        }

        // frames_to_deliver = frames_to_deliver > 4096 ? 4096 : frames_to_deliver;
        frames_to_deliver = frames_to_deliver > (48*2) ? (48*2) : frames_to_deliver;

        /* deliver the data */

        if (playback_callback (frames_to_deliver) != frames_to_deliver) {
                fprintf (stderr, "playback callback failed\n");
            break;
        }
    }

    snd_pcm_close (playback_handle);
    exit (0);
    
    return 0;
}
Can someone please point to me what I am doing wrong in attempts 1 and 2 above?

Last edited by mahaju; 10-13-2021 at 01:25 AM.
 
Old 10-13-2021, 03:33 PM   #2
GazL
LQ Veteran
 
Registered: May 2008
Posts: 6,202
Blog Entries: 1

Rep: Reputation: 4267Reputation: 4267Reputation: 4267Reputation: 4267Reputation: 4267Reputation: 4267Reputation: 4267Reputation: 4267Reputation: 4267Reputation: 4267Reputation: 4267
Issue one is because you're trying to compile C code as C++.

"format" is a snd_pcm_format_t, which in turn is an enum. Unlike C, where enums are just type int, in C++ they are their own unique class type, and that class won't have a ++ operator unless you define one with an operator overload.

Your best bet is to just write/compile it as C, but if you insist on using C++ rather than C then you can either implement a operator++ for the enum class, or try using static_cast<int>() in the for loop. Either way, it's going to be a little ugly, and you're possibly going to hit further issues.


Can't help you with issue two. PCM stream sign waves not sounding "right" is a little out of my comfort zone.
 
Old 10-13-2021, 08:14 PM   #3
mahaju
Member
 
Registered: Nov 2011
Posts: 39

Original Poster
Rep: Reputation: Disabled
Compiling as C++ turned out to be the issue, if I change the filename to main.c it compiles without any errors. But I need to use the audio interfacing features in this program later on in a C++ application so I have to do this as C++. Thanks for your help, I'll find some other way to do this instead.
 
Old 10-14-2021, 05:20 AM   #4
GazL
LQ Veteran
 
Registered: May 2008
Posts: 6,202
Blog Entries: 1

Rep: Reputation: 4267Reputation: 4267Reputation: 4267Reputation: 4267Reputation: 4267Reputation: 4267Reputation: 4267Reputation: 4267Reputation: 4267Reputation: 4267Reputation: 4267
You're welcome. I guess you have a little bit of C to C++ porting to do then.

The way they find the format value in that '-o' switch section is a bit weird anyway. I can't see why they didn't just use something like:
Code:
format = snd_pcm_format_value(optarg);
if (format == SND_PCM_FORMAT_UNKNOWN) {
   printf("Unknown format, setting to default S16\n");
   format = SND_PCM_FORMAT_S16;
}

Last edited by GazL; 10-14-2021 at 05:24 AM. Reason: removed endianness from example.
 
Old 10-14-2021, 08:11 AM   #5
ntubski
Senior Member
 
Registered: Nov 2005
Distribution: Debian, Arch
Posts: 3,620

Rep: Reputation: 1977Reputation: 1977Reputation: 1977Reputation: 1977Reputation: 1977Reputation: 1977Reputation: 1977Reputation: 1977Reputation: 1977Reputation: 1977Reputation: 1977
Quote:
short buf[48*2]; // try 1000 Hz @48000, 48 samples @48000 = 1 ms
My experience with networked music programs (which try to have the minimum possible latency) is that most soundcards won't be able to work well with a buffer size of 1ms (though I'm not entirely sure if the buffer size you have here really corresponds to the option in those programs). Have you tried with a larger buffer? Maybe aim for around 10ms to start.

Quote:
Originally Posted by GazL View Post
The way they find the format value in that '-o' switch section is a bit weird anyway. I can't see why they didn't just use something like:
I guess they really want to be case insensitive.
 
Old 10-14-2021, 08:29 AM   #6
GazL
LQ Veteran
 
Registered: May 2008
Posts: 6,202
Blog Entries: 1

Rep: Reputation: 4267Reputation: 4267Reputation: 4267Reputation: 4267Reputation: 4267Reputation: 4267Reputation: 4267Reputation: 4267Reputation: 4267Reputation: 4267Reputation: 4267
Quote:
Originally Posted by ntubski View Post
I guess they really want to be case insensitive.
Yes, that was my initial thought too when viewing that code, but then I saw that snd_pcm_format_value is case-insensitive.

Perhaps that example dates back to a time when it wasn't. *shrug*

Last edited by GazL; 10-14-2021 at 08:34 AM.
 
Old 10-14-2021, 08:38 AM   #7
EdGr
Member
 
Registered: Dec 2010
Location: California, USA
Distribution: I run my own OS
Posts: 726

Rep: Reputation: 345Reputation: 345Reputation: 345Reputation: 345
Quote:
Originally Posted by ntubski View Post
My experience with networked music programs (which try to have the minimum possible latency) is that most soundcards won't be able to work well with a buffer size of 1ms (though I'm not entirely sure if the buffer size you have here really corresponds to the option in those programs). Have you tried with a larger buffer? Maybe aim for around 10ms to start.
That is my theory as well. Audio frames typically have 4-8K samples.

Another potential problem is that the ALSA hardware calls will fail if PulseAudio is in use. The OP needs to avoid those calls or switch to the PulseAudio API.
Ed
 
Old 10-14-2021, 10:00 AM   #8
business_kid
LQ Guru
 
Registered: Jan 2006
Location: Ireland
Distribution: Slackware, RPi OS, Mint & Android
Posts: 13,200

Rep: Reputation: 1811Reputation: 1811Reputation: 1811Reputation: 1811Reputation: 1811Reputation: 1811Reputation: 1811Reputation: 1811Reputation: 1811Reputation: 1811Reputation: 1811
mahaju, if you want my 0.02́ worth, that's a dumb idea.

I'm an Electronics hardware guy, and sinewaves happen naturally around inductors, capacitors, valves and Analogue circuitry. Computers are not Analogue, they are Digital; it's the difference between rounded edges (Sine waves) and Square ones(Digital). It can be done, but not easily. You are using a slow, intellectually challenged cpu with no (sorely needed) maths processor, low memory and probably using linux, which is not a Real Time Operating System (RTOS). In other words, you have stacked the deck against yourself. It's also totally unnecessary work when you can buy wave generator ICs which do the job for cheap.

Horses for courses, mahaju.
 
Old 10-14-2021, 07:22 PM   #9
ntubski
Senior Member
 
Registered: Nov 2005
Distribution: Debian, Arch
Posts: 3,620

Rep: Reputation: 1977Reputation: 1977Reputation: 1977Reputation: 1977Reputation: 1977Reputation: 1977Reputation: 1977Reputation: 1977Reputation: 1977Reputation: 1977Reputation: 1977
Quote:
Originally Posted by GazL View Post
Perhaps that example dates back to a time when it wasn't. *shrug*
Or programming defensively against a time in the future when it won't be?

Quote:
Originally Posted by EdGr View Post
Another potential problem is that the ALSA hardware calls will fail if PulseAudio is in use.
OP reported getting some (distorted) sound out, so it's probably not that.

Quote:
Originally Posted by business_kid View Post
Computers are not Analogue, they are Digital; it's the difference between rounded edges (Sine waves) and Square ones(Digital). It can be done, but not easily.
Uh, do you really think any human would be able to hear the difference between a perfectly rounded edge sine wave, versus the approximation produced by the standard C library sin() function?
 
Old 10-15-2021, 06:45 AM   #10
business_kid
LQ Guru
 
Registered: Jan 2006
Location: Ireland
Distribution: Slackware, RPi OS, Mint & Android
Posts: 13,200

Rep: Reputation: 1811Reputation: 1811Reputation: 1811Reputation: 1811Reputation: 1811Reputation: 1811Reputation: 1811Reputation: 1811Reputation: 1811Reputation: 1811Reputation: 1811
Quote:
Originally Posted by ntubski
Uh, do you really think any human would be able to hear the difference between a perfectly rounded edge sine wave, versus the approximation produced by the standard C library sin() function?
Having come through the teeth of the "Valve sound" vs. "Transistor Sound" argument, I gave up trying to divine what people would hear the difference between decades ago, and never got into serious hi-fi.The valve sound lasted longer here than in most places. For those who never went there, valves added about 10%THD and some other forms of distortion as well, but the sound was preferred by many. Valves lost out economically, and servicing costs were much higher. They also needed various power supplies, power resistors and transformers, meaning you had to build big and plug it in. Transistorised radios could be made to fit in your pocket with batteries.

I had a pi 0. There's no FPU, and it's slow. I wouldn't write anything for it. The sound of a 1khz sine wave sucks in any case. The OP will do what he likes. I gave my opinion.

Last edited by business_kid; 10-15-2021 at 06:47 AM.
 
  


Reply


Thread Tools Search this Thread
Search this Thread:

Advanced Search

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off



Similar Threads
Thread Thread Starter Forum Replies Last Post
generating k permutations of a series and then generating all possible binary trees from them jamesbon Programming 5 09-27-2018 01:42 AM
LXer: Raspberry Pi Zero WH adds 40-pin GPIO header to Zero W LXer Syndicated Linux News 0 01-14-2018 05:40 AM
LXer: Ubuntu's Snapd Daemon Now Works Properly on Raspberry Pi and Raspberry Pi Zero LXer Syndicated Linux News 0 06-15-2017 03:22 PM
LXer: Raspberry Pi Foundation: We'll Ship the 250,000th Raspberry Pi Zero W This Week LXer Syndicated Linux News 0 05-04-2017 06:05 PM

LinuxQuestions.org > Forums > Non-*NIX Forums > Programming

All times are GMT -5. The time now is 10:32 AM.

Main Menu
Advertisement
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
Open Source Consulting | Domain Registration