Changing the play back rate of a buffer in C? - c

I am using an Altera DE2 FPGA board and playing around with the SD card port and audio Line Out. I'm programming in VHDL and C, but the C portion is where I'm stuck due to lack of experience/knowledge.
Currently, I can play a .wav file from the SD card to the Line Out. I'm doing this by reading and sending the SD card data > FIFO > Audio Codec > Line Out. Ignoring all the other details, the code simply is:
UINT16 Tmp1=0;
...
Tmp1=(Buffer[i+1]<<8)|Buffer[i]; //loads the data from the SD card to Tmp1
//change the buffer rate?
IOWR(AUDIO_BASE, 0, Tmp1); //sends Tmp1 data to Line Out
If I were to print Tmp1, it's basically the points on a sine wave. What I want to do now is fiddle with how the sound plays by changing the play back rate (ideally I want to play the sound up or down an octave, which is just double or half the frequency). Can anyone provide some suggestions on how I can do this in the section:
//change the buffer rate?
Is it possible in C to write a few lines of code in that section to obtain what I'm looking for? ie. change how fast I'm reading from the Tmp1 buffer to the AUDIO_BASE.
Thanks in advance!
~Sarengo

If the IOWR interface provides no such option then you will have to do it yourself: You have to re-sample the sound. The theory can be found here 1 here 2 here 3 and here 4.
Raising the freqency by a multiple is easy: Just drop some samples, eg lower the freqency by factor 2 by just dropping every second sample from the buffer so that it then has half the size.
Lowering the frequency is harder because you need information you dont have: the samples in-between samples. You could start with simple linear interpolation and if you think that it does not sound good enough you can change it for something more advanced. Eg you can half the frequency by inserting a sample between two samples with their average value. If your waveform looks like this: 5 9 7 3 you would get 5 7 9 8 7 5 3

Related

Log data from MPU6050 through serial (UART) fails (data loss)

here is the problem I am facing. I have interfaced my ATmega328P with a 6-axis IMU (MPU6050 with the GY521 breakout board). I can read data through the TWI interface (Atmel's I2C) and send it to my PC (running Ubuntu) via the UART. I am using custom-built libraries for both these communication protocols, but they are pretty standard and seem to work just fine. The goal of the project is to compute orientation data from the IMU readings in real-time, say at 100 Hz.
The main problem is that I cannot log data from the device at 100 Hz (not even at 50 Hz). The orientation filter I am using (here) requires a quite high frequency and 100 Hz turned out to work fine (tested offline acquiring data from another device).
Right now, I am using the 16-bit timer of the ATmega328P to sample data at 100 Hz and this seem to work, as I have added to the ISR a line to toggle the built-in LED and it looks to me that it is blinking at 100 Hz (I can barely see it turning on and off). In the same ISR, I read the values from the inertial sensor and, just to log them, send these values through the serial port. Every 10 ms (maximum), I send 9 floats (36 bytes) with a baud rate of 115200. If I use the Arduino IDE's Serial Monitor to visualize this data stream, I notice something very weird, as in the following screenshot.
https://imgur.com/zTBdkhv
As you notice taking a look at the timestamps, there is a common 33 ms delay every 2 or 3 sets of samples received. Moreover, I get roughly the 60% of the data. For example, an acquisition of 10 seconds only gets me less than 600 samples (per each variable) instead of 1000. Moreover, I tested the same sending only one variable through the UART (i.e. only a single float, 4 bytes) and this results in the same behavior!
By the way, I am exploiting the following to send each byte (char) via the UART interface.
void writeCharUART(char c) {
loop_until_bit_is_set(UCSR0A, UDRE0);
UDR0 = c;
}
Even though my ISR runs at 100 Hz (LED blinking seem to confirm that), data loss may occur at the level of the TWI transmission. To prove that, I modified the code of the ISR to send just a normal char (T) instead of data from the MPU and I got a similar behavior. Something like this:
00:10:05.203 -> T
00:10:05.203 -> T
00:10:05.236 -> T
00:10:05.236 -> T
00:10:05.236 -> T
00:10:05.236 -> T
00:10:05.269 -> T
So, I guess there is something wrong with the UART library and I actually sample at 100 Hz, but the logging frequency is much lower (and not constant). How can I solve this issue and/or debug the UART library? Do you see other reasons to justify this issue?
EDIT 1
As pointed out in the comments, it seems to be a problem of the receiving software that limits the frequency to ~30 Hz by some sort of buffering. To confirm that, I programmed the ATmega328P with the following code (this time using the IDE).
void loop() {
Serial.println("T");
}
At first, I thought there was no delay this time, but I could find it after 208 samples. So, there are ~200 samples received at the same timestamp and another bunch of samples after 33 ms. This may be proof that the receiving software introduces this delay.
I also tested a simple serial monitor that I had developed in C and, even though there is no timestamp functionality, I am also loosing samples if I fix the duration of the acquisition sampling at 100 Hz. My serial monitor is based on the termios.h library, but I could not find any documentation about its way of buffering incoming data.
There are two issues here:
You are missing messages. You checked the sample rate just with your eyes and told us that you can still see a very fast blinking. Depending on the colour of your LED, the ambient light, your physical state, and your eyes this could mean anything from 30 Hz to 100 Hz.
I would not trust my eyes to estimate and rather use an oscilloscope or a frequency counter to measure.
You could reduce the frequency of the LED blinking to 1Hz or even lower by dividing in software. Such a low frequency can be measured by hand via a stop watch. For example count 30 blinks and check the time needed for this.
Add a counter to the message and increment it with each message. You will see it right away if you're losing data.
The timestamps seem to indicate that the messages are "clustered" at about 30 Hz.
I'm guessing that the source of the timestamp in running at 30 Hz. So it can not give you more accurate values.
I kind of solved my issues! First of all, thanks to the comments I have checked that my ISR was correctly running at 100 Hz. Doing so, I could be sure that the problem where somewhere else, namely in the UART communication.
I found this very helpful: Linux, serial port, non-buffering mode
Apparently, the Serial Monitor provided by the Arduino IDE uses exploits the termios.h library and uses its default settings. I checked also the user manual and switched to the polling-read mode. Quoting from the user manual
If data is available, read(2) returns immediately, with the lesser of the number of bytes available, or the number of bytes requested. If no data is available, read(2) returns 0.
Hence, I switched back to my serial monitor code and changed the initPort() function adding the following lines of code.
struct termios options;
(...)
options.c_cc[VTIME] = 0;
options.c_cc[VMIN] = 0;
I noticed right away a much higher data frequency in the terminal. I kept the 1 Hz LED blinking in the ISR and there is no period stretching. Moreover, an acquisition of 10 seconds this time gave me roughly 1000 samples per variable, consistent with a sampling rate of 100 Hz.
On the AVR side, I also changed the way I send data through the UART. Before, I was sending 9 floats like this:
sprintf(buffer, "%f, %f, %f", value1_x, value1_y, value1_z);
serial_print(buffer); // no "\n" sent here
sprintf(buffer, "%f, %f, %f", value2_x, value2_y, value2_z);
serial_print(buffer); // again, no "\n" sent
sprintf(buffer, "%f, %f, %f", roll, pitch, yaw);
serial_println(buffer); // "\n" is sent here once the last data byte is sent
Now, I replaced all this with a single call to the function serial_println() and I write only 6 floats to the buffer.

Send Tone from arduino through mono aux cord to speakers via 3.5mm jack

Hello I am using Arduino C to program my microcontroller to output frequencies via tone(); to a piezo buzzer. Currently the frequency(s) is/are chosen with a potentiometer. I would like to ditch the piezo buzzer and instead output the frequency to a 3.5mm headphone jack, where either headphones can be plugged in or a mono auxiliary cord, with a pair of desktop speakers on the other end. What is the best and most efficient way to do this as far as coding/translating the frequency to be output over the 3.5mm jack?
update :
so for my 3.5mm audio jack, i have the 10k ohm resistor inline with the ground connection and then one pin running to positive lead and the other pin running to digital pin 4 on my arduino. I have tried testing multiple frequencies on a pair of desktop speakers with a subwoofer, and comparing them to an actual tone generator app i have on my phone. my prototype seems to emit more of a noisy/fuzzy sound compared to the tone generator app which seems to be a lot more crisp/clean. Also frequencies under 100 Hz arent playing what they should sound like, however the app outputs frequencies under 100 Hz just fine. My three questions are:
1. How can I get to output to be as close as possible to the actual frequency?
2. How can i get the output to be crisp and clean, not noisy and fuzzy?
3. Is there something i'm missing/any ideas how to make the frequencies go below 100 Hz?
i know theres 5 pins on the 3.5mm audio jack but im only using 3, could this be an issue?
please feel free to ask any questions, i can also upload pictures as needed.
It's exactly the same thing- just send the square wave through the audio jack. Assuming your speakers have their own amplifier (which most desktop speakers do), you should just put a 10k resistor on the output line. Just hook up the pin and ground to the jack and you should be fine.

Left and Right audio channels are exchanging

I am trying to write an application for capturing stereo audio. My audio input has two channels(Stereo). I am writing this audio data into a wav file. Some times these audio channels are exchanging i.e, Left becomes right and right becomes left. This is happening only if i open and close the device file or turn off the device and turn it on. And it is happening randomly. I don't want channels to be exchanged. Please suggest.
stereo PCM stored in a wav file is in an LR format. 'L' stands for left channel sample and 'R' for right channel sample. I guess you have a bug in retrieving or storing the PCM. Maybe sometimes you start with the right (correct) position in buffer and sometimes you start with the second sample. It's hard to tell without additional info.

Playing the solfege notes with the ALSA API?

I'm playing with the Alsa API and I wonder which parameters I should pass to the function snd_pcm_writei to simply play the solfège syllables/notes (A-G / do re mi fa sol la si do).
Thanks
If you really want to do it with that function, generate a waveform in a buffer. A triangle-shaped wave may not sound too awful and should be simple enough to generate.
The base "la" (A) is 440Hz, that is, 440 cycles of the waveform of your choice per second.
The other notes can be obtained by multiplying/dividing by 2^(1/12) (1.05946309) for each half tone above/below this base frequency. You will need to know at what frequency the output device is set up (that's probably an argument to another ALSA function). If the device frequency is, say, 44100 Hz, and you want to play the base "la", each period of your waveform should occupy 44100 / 440 or about 100 samples. Pay attention to the sample width and the number of channels the device is configured for, too.
Explanation: there are 12 half tones in an octave, and an octave is exactly half (lower pitched) or double (higher pitched) the frequency. Once you have multiplied 12 times by 2^(1/12), you have multiplied by 2, so each half-tone is at a factor of 2^(1/12) above the previous one.
Sounds like you want midi, not ALSA. ALSA deals with sampled audio (e.g. digital waveforms derived from a CD, wav, mp3, etc). It is not a sound synthesis program.

playing only part of a sound using FMOD

I'm trying to play only part of a sound using FMOD, say frames 50000-100000 of a 200000 frame file.
I have found a couple of ways to seek forward (i.e. to start playback at frame 50000) but I have not found a way to make sure the sound stops playing at 100000. Is there any way FMOD can natively do this without having to add lbsndfile or the like into the picture?
I should also mention that I am using the streaming option. I have to assume that these sounds are arbitrarily large and cannot be comfortably/quickly loaded into memory.
You can use Channel::setDelay for sample accurate starting and stopping of sounds. Use FMOD_DELAYTYPE_DSPCLOCK_START to set the start time of the sound and FMOD_DELAYTYPE_DSPCLOCK_END to set the end time.
Check out the docs for Channel::setDelay, FMOD_DELAYTYPE, System::getDSPClock.
You should be able to use the streaming callback to stop the stream when you get to the desired point.
Option 1: When you create the stream, set lenbytes to an even divisor of the number of frames you wish to play. In your example, set 'lenbytes' to 5000, then keep a counter in the callback. When you get to 10, stop the stream.
Option 2: use FSOUND_Stream_AddSyncPoint with pcmoffset set to your desired stopping point. Register a callback with FSOUND_Stream_SetSyncCallback. Stop the stream in the callback.
To start playback at sample 50,000 and end at 100,000 you could do the following assuming the sound file sample rate and the system sample rate are the same. As DSP clock works in system output samples you may need to do some maths to adjust your end sample in terms of output rate. See Sound::getDefaults for sound sample rate and System::getSoftwareFormat for system rate.
unsigned int sysHi, sysLo;
// ... create sound, play sound paused ...
// Seek the data to the desired start offset
channel->setPosition(50000, FMOD_TIMEUNIT_PCM);
// For accurate sample playback get the current system "tick"
system->getDSPClock(&sysHi, &sysLo);
// Set start offset to a couple of "mixes" in the future, 2048 samples is far enough in the future to avoid issues with mixer timings
FMOD_64BIT_ADD(sysHi, sysLo, 0, 2048);
channel->setDelay(FMOD_DELAYTYPE_DSPCLOCK_START, sysHi, sysLo);
// Set end offset for 50,000 samples from our start time, which means the end sample will be 100,000
FMOD_64BIT_ADD(sysHi, sysLo, 0, 50000);
channel->setDelay(FMOD_DELAYTYPE_DSPCLOCK_END, sysHi, sysLo);
// ... unpause sound ...

Resources