Go Down

Topic: Is simultaneous advanced wave synthesis possible with TVout ? (Read 8635 times) previous topic - next topic

smultron

Hello all

I'm working on an Outrun clone for the Arduino. Graphics is coming along fine, I have rolling hills and curves and alpha bitmaps etc.

But sound is a major issue.

I want to use sawtooth waves, triangle waves and stuff to make 'proper' Atariesque sounds. I have already tested 1-bit wave synthesis on the PC, but I am not sure of what is the best plan on attack on the atmega328.

Timer0 is taken for the clock, timer1 is needed by TV signal generation. TVout.tone uses timer2.

Is it possible to generate for example proper sawtooth waves with the remaining timer2? I have plenty graphics coding background but very little knowledge of audio processing.

Has anyone created advanced waves _while_ using tvout and if so, how?

janost

Yes, you can use the remaining timer as an 8-bit PWM DAC.
Set it up for 62.5KHz pwm.

Then use phaseaccumulators for creating and adding sawoscillators.
Basically you need 16-bit phaccs with 16-bit increments and add the MSB of them to create the sample.

The adding and PCM output needs to go into you horizontal blanking in the video ISR.

That should give you about 3 saw oscillators.

smultron

Thank you Janost, this is exactly the kind of pointers i was hoping for.

I have been looking at Matt Sarnoff's 1-bit AVR "Nastysynth" for atmega48 and timer1.

https://github.com/74hc595/1-Bit-AVR-Synthesizer/blob/master/1bitsynth.c

Does this (in your audio coding experience) contain the kind of elements that I require for this task ? It looks like Matt is using the kind of 16-bit phaseaccumulators that you're talking about.

I still haven't looked at the code close enough to determine if he's using a 16-bit timer, in which case porting the code requires some extra work.

Could you explain the concept in just a teeny weeny little more detail ? I can figure out the pwm settings and such, but more the audio-specific implementation of phase accumulators and how they combine with the pwm ?


janost

The PWM on timer2 is just a digital to analog converter.
Writing 255 to it gives 5volt on the output pin and 128 gives 2.5volt and so on.

The phaseaccumulator is the oscillator.

If you add 0x100 as the increment and write the MSB to the DAC you have a sawwave out at 61Hz.
Adding 0x80 your phacc the output will be 30.5Hz and 0x200 gives 120Hz.
A variable increment with a resolution of 8bits.

If you want other waveforms the MSB of the phacc is used as a pointer to a wavetable of any waveform.

You can read the concept of DDS here:
http://www.electricdruid.net/index.php?page=info.dds


smultron

OK, so now i have had time to investigate all the information you've given me. You have already helped me a lot to grasp the basic concepts.

Let's see if I've understood correctly:

1. timer2 is set to PWM mode. Its only function is to act as a DAC, the output voltage is controlled by the duty cycle set by OCR2. I presume were talking fast PWM mode ?

2. prescaler is set to clk/256 to get 62,5 kHz, this is just a matter of convenience (0xff = 100% duty).

3. since I'm using TCCR2B, Arduino pin 11 is the output pin (as is required)

4. phase accumulator is 16-bit, incremented with a 8-bit increment, so that I can get sub-60Hz frequencies

5. I put the phase accumulator increment in the TVout hbi_hook. I take the MSB of the phacc and put it in OCR2 ie. PWM duty

6. Alternatively I use the phacc as a pointer to a 8-bit table of values that define the waveform.

That it ?

Cheers !

janost

Yes, thats right.

If you want more than 1 oscillator you use more than 1 phacc/increment and add the MSBs together.

With a phase increment of zero the oscillator is silent.

That's it :)

smultron

#6
Apr 29, 2014, 10:03 am Last Edit: Apr 29, 2014, 05:27 pm by smultron Reason: 1
A little update on this, in case someone else is interested. I have found some things that need to be taken into account

Fast PWM mode details:

"1. timer2 is set to PWM mode..."

= timer 2 should be in fast PWM mode 3, where TOP is fixed at 0xFF and OCR2A controls duty cycle. The other mode, the fast PWM mode 7 reserves OCR2A for holding top and duty is controlled by OCR2B. This however means that output will be Arduino pin 10 = incompatible with game shield and Nootropic's Hackvision. Also, controlling PWM frequency is not required in this case.

"2. prescaler is set to clk/256 to get 62,5 kHz, this is just a matter of convenience (0xff = 100% duty)."

Actually, since TOP is at 0xFF to get 62,5 kHz prescaler is 1 =>  Frequency = clockspeed / 256 / prescaler.

I am close to working example with this project.

I have simulated the waveform synthesis on a PC using SFML library and a 8kHz signal. So far I have implemented:
1. 2 oscillators with 7 waveforms + noise
2. ADSR envelope
3. oscillators can be combined in several modes: mixed, AM modulated, FM modulated and low-pass filter
4. software low-pass filter for resultant wave

UPDATE:

Using this method, a simple toggle of OCR2A value in the hbi_hook with resolution 128x96 produces a square wave at 7811 hz



smultron

#8
Apr 29, 2014, 11:07 pm Last Edit: Apr 30, 2014, 10:24 am by smultron Reason: 1
This is a video of the crude concept running on my SFML port of TV_out. Audio signal is emulated by a 8kHz generated sample. Due to a bad bug in ALSA lib, refreshing sound buffer dynamically is broken = reason why sound is cut when synthesis parameters are changed. This will not be an issue on the Arduino.

The sound & video quality is horrible. Sorry.

http://www.youtube.com/watch?v=IAVWaAS_2vQ&feature=youtube_gdata_player

UPDATE:

Unfortunatey, on the Arduino, I am getting interference that is messing up the tv signal... I'll need to troubleshoot that.


smultron

I came up with a new idea while thinking about what to do with the interference.

When I was playing with my oscilloscope and was running fast PWM by mistake in mode 7 (OCR2A holds TOP, OCR2B controls duty cycle) I noticed I can get much higher frequencies by dropping TOP (ofcourse!).

Now, 62.5 kHz is maximum output frequency with 'normal' fast PWM when prescaler set at 1. But my thinking is that the higher the PWM frequency is, the cleaner the signal. Setting TOP at 127 for example, you get a PWM signal of whopping 125 kHz ! And due to the way that the PWM mode operates, this means that output is maxed at 5 v when OCR2A=127 and OCR2B=127.

In summary, by using this trick (fast PWM mode 7) it should be possible to get a better PWM frequency for DDS synthesis while sacrificing DAC resolution (example 127 levels instead of 256 yields 125 kHz signal, 64 levels of DAC would give 250 kHz signal).


janost

The sound is better with more bits (8 vs. 7) and the lower PWM frequency (62.5KHz vs. 125KHz) than the other way around.

PWM will work as long as its frequency is equal or above the sampling frequency.


janost

#12
May 01, 2014, 10:35 pm Last Edit: May 01, 2014, 10:38 pm by janost Reason: 1
You can have my dsp filter code for free.
It's a 18dB lowpass resonant filter and it sounds awesome.

input and output are signed 16-bit int and CUTOFF and RESONANCE are 8-bit unsigned int.

Uses only 132 CPU cycles.

Code: [Select]

// 18dB 3pole lowpass DCF with resonance. The MX-filter
     coefficient=CUTOFF^0xFF;  
     #define M(MX, MX1, MX2) \
     asm volatile ( \
       "clr r26 \n\t"\
       "mulsu %B1, %A2 \n\t"\
       "movw %A0, r0 \n\t"\
       "mul %A1, %A2 \n\t"\
       "add %A0, r1 \n\t"\
       "adc %B0, r26 \n\t"\
       "clr r1 \n\t"\
       : \
       "=&r" (MX) \
       : \
       "a" (MX1), \
       "a" (MX2) \
       :\
       "r26"\
     )
     M(MX1, M0, coefficient);
     M(MX2, M1, CUTOFF);
     M0=MX1+MX2;
     M(MX1, M1, coefficient);
     M(MX2, M2, CUTOFF);
     M1=MX1+MX2;
     M(MX1, M2, coefficient);
     M(MX2, M3, CUTOFF);
     M2=MX1+MX2;
     M(MX1, M3, coefficient);
     M(MX2, M4, CUTOFF);
     M3=MX1+MX2;
     M(MX1, M4, coefficient);
     M(MX2, M5, CUTOFF);
     M4=MX1+MX2;
     M(MX1, M5, coefficient);
     M(MX2, M6, CUTOFF);
     M5=MX1+MX2;
     M(MX1, M0, RESONANCE);
     M6 = input-MX1;
     output=M0;
code]

smultron

Much appreciated ! I modified my low-pass filter code from an example on Microchip forums and I'm not even certain how well it works with audio.

This is the code from the PC version that uses floats. I don't even really understand what it does, but I can see the results on the scope though: it dampens down signals progressively more as you go up in frequency. The problem is that at times it causes horrible distortion (signal goes bezerk & over limits), and I haven't figured out how to keep it under control.

The reason why the calculations have been spread out on many lines was precisely because I was stepping through the code trying to figure out how TAU and DT affect the result.

In fact, if you could point me yet again to some theory on how low-pass filters work, I'd be very interested in learning more.
I know synthesizers can control the lo-pass filter with an envelope, but what are the mathematical parameters that are being manipulated ?

Code: [Select]


// Low pass filter

#define SF10 (10)
#define SCALE10 (2^SF10)
#define DT (0.01f) // time, was 0.01 originally
#define TAU (0.9f) // time constant in sec/rad, was 0.9 originally
                    // 0.9 = distorts at 70 hz, vol = 62
                    // 0.45 = distorts at 180 hz
                    // 0.12 = distorts at 440 hz
#define K ((float)DT/((float)TAU+(float)DT))
#define K_SCALE10 (K*(float)SCALE10) // Note this is 102, truncation of 0.4 doesn't affect filter really
#define FILTER_IC (long)(0.0*SCALE10) // Note this is 0 as a long

// low pass filter
int lag1order( int x, long last_y, float tau, int amp )
{
long x_scaled;
float temp;
static long y_scaled;

temp = K;
temp = K_SCALE10;
y_scaled = last_y;
x_scaled = x*SCALE10;
temp = x_scaled - y_scaled;
temp = temp * ((float)DT/((float)tau+(float)DT))* (float)SCALE10;
temp = temp / SCALE10;
y_scaled = y_scaled + temp;
return(y_scaled*amp/SCALE10);
}

janost

That code is extremely slow.
Don't use floats.

My code does basically the same.
A bunch of multiplications and averaging.

Add some feedback and you have just the right thing.

I spent years writing the filter.
Trying like you to figure out what it does.
When I knew how it works it wasn't that difficult.

Use it. Its faster than your float code.
This one is in Assembly :)

Plenty faster. :)

Go Up