PDA

View Full Version : Writing an audiogenerator filter, where to start


hanfrunz
7th February 2006, 21:49
Hello everyone,

i like to write a simple audiogenerator: sinus waves, noise, maybe a SMPTE timecode generator (LTC). I haven't any knowledge about how avisynth handles audio at all. Is there a very simple filter to start with? Is there a funcion like getframe when you deal with audio?

thanks,
hanfrunz

Wilbert
7th February 2006, 22:06
Did you look at http://www.avisynth.org/Tone?

hanfrunz
7th February 2006, 22:10
Did you look at http://www.avisynth.org/Tone?

mmh, i didn't even know that this filter exists :confused: cool. I will check the source and try to start my timecode generator project.

thanks,
hanfrunz

tateu
8th February 2006, 01:22
I would love a timecode generator/reader. I spent a day looking into writing my own a few weeks ago. I got side tracked and never really got anywhere with it.

You didn't ask for links on timecode info, but here are some I found, in case they might be useful...
http://www.philrees.co.uk/articles/timecode.htm
and SGI has code for reading LTC and VITC from a wav file in libdmedia:
http://www.lurkertech.com/lg/timecode.html

hanfrunz
8th February 2006, 14:32
I would love a timecode generator/reader. I spent a day looking into writing my own a few weeks ago. I got side tracked and never really got anywhere with it.

You didn't ask for links on timecode info, but here are some I found, in case they might be useful...
http://www.philrees.co.uk/articles/timecode.htm
and SGI has code for reading LTC and VITC from a wav file in libdmedia:
http://www.lurkertech.com/lg/timecode.html

hello tateu,
I will start with a generator. A reader is much more difficult. Thanks for the links. I got all the SMPTE-standards as well. But first of all i have to understand how all that soundstuff works in avisynth. I started reading the sourcecode of the TONE filter. It looks not too complicated.

hanfrunz

Wilbert
8th February 2006, 16:01
I will start with a generator. A reader is much more difficult. Thanks for the links. I got all the SMPTE-standards as well.
generator: http://www.avisynth.org/Showframes.

hanfrunz
8th February 2006, 16:51
generator: http://www.avisynth.org/Showframes.

yes, but we want to read/generate a LTC on an audiotrack. Showframes just shows a SMPTE timecode human readable on the screen.

tateu
13th February 2006, 09:10
I hope you don't mind me stepping on your toes here...I had some free time this weekend and I've wanted to play with a timecode generator for years, so I decided to devote some time to it.

I spent several hours on it, not coming up with anything useful, but then everything just sort of snapped into place. It seems so simple now, I am not sure why I didn't figure it out sooner.

I have two versions, a standalone cli exe that creates 16bit wavs and an avisynth version. I'm having some trouble with the avisynth version but the cli version seems to be working correctly...Though I don't have a timecode reader available to test if the files I am creating are actually correct. I have been visually comparing them to files downloaded from the internet. Maybe I should hold off on congratulating myself until I can get the output plugged into a deck or something and tested.

The avisynth versions seems to be working if I open the script in VirtualDub and save as it as a wave, but with every other program I have tried (media player, mp3 and aac audio encoders, etc.) it is skipping over data. It writes correct timecode for a second or so, followed by a second or so of silence repeated throughout the entire file. It's probably an easy fix dealing with how I use the "start" and "count" parameters of avisynth's GetAudio function but I haven't found it yet.

If you're interested in taking a look at my code, here's the exe version...
http://www.tateu.net/software/dl.php?f=LTC_Gen_cli
The actual timecode parsing functions are contained in "LTCFunctions.cpp." I am not posting the avisynth version yet, but it uses the same "LTCFunctions.cpp" file.

"LTC_Gen.exe -h"
- to get a list of parameters.

"LTC_Gen.exe"
- will use default parameters and create "output.wav" which is a 1 minute (1800 frame) 44.1KHz 16 bit stereo wav file containing 30fps non-drop time code starting at 01:00:00:00 and at half amplitude.

"LTC_Gen.exe -o 25_1_48000.wav -s 48000 -c 1 -t 02:01:00:00 -d 1500 -f 25 -n 0 -a 1.0"
- will create "25_1_48000.wav" which is a 1 minute (1500 frame) 48KHz 16 bit mono wav file containing 25fps time code starting at 02:01:00:00 and at full amplitude.

Next up, is probably to create the functions for reading timecode from a wav file, then I'll look into fixing up the avisynth version and adding 24fps and 29.97fps support (if they are valid for the smpte timecode spec).

hanfrunz
13th February 2006, 11:31
@tateu: cool, i started the project in my head only, i had no time the last days because a big filmfestival is in town. I'll check the source. Maybe i can help you with the avisynth version.

hanfrunz

tateu
13th February 2006, 22:54
Here's a new cli version that fixes a problem with 30fps in drop frame mode:
http://www.tateu.net/software/dl.php?f=LTC_Gen_cli

And the avisynth version (still not working correctly with most programs but seems fine with avs2wav and VirtualDub):
http://www.tateu.net/software/dl.php?f=LTCGen_Avisynth

LoadPlugin("LTCGen.dll")
mDur = 1800
mFPS = 30
mSampleRate = 44100

video= BlankClip(length=mDur, width=320, height=240, fps=mFPS, \
audio_rate=mSampleRate, stereo=true, color=$000000)
audio = LTCGen(samplerate=mSampleRate, channels=2, start="01:00:00:00", \
duration=mDur, fps=mFPS, dropframe=0, amplify=0.5)

AudioDub(video, audio)

Mug Funky
14th February 2006, 02:38
@ tateu:

how do i dropped frame? or rather, i'd like to make a TC based version of trim in script only, but so far have only been able to make it work for PAL timecode. can't quite get drop-frame to work nicely. i know the principle behind it but get bored while trying to implement it...

would you be so kind as to post a code snippet (in whatever language really... don't go nuts) that turns a frame number into DF TC, or vice versa?

[edit]

nm, i'm reading the source. gonna take a while, i'm a bit of a nunce when it comes to C :)

tateu
14th February 2006, 04:15
Mug Funky,

Give this a try (may not be the cleanest way, but it's what I've always used):

function DropFrameOffset(int mHours, int mMinutes, int mSeconds) {
return 2 * ((mHours * 60 + mMinutes) - (((mHours * 60) + mMinutes) / 10))
}

function Frames_to_TimeCode(int mFrameCount, int mFPS, bool bDropFrame) {
mFrames = mFrameCount % mFPS
mSeconds = int((mFrameCount % (60 * mFPS)) / mFPS)
mMinutes = int((mFrameCount % (60 * 60 * mFPS)) / (60 * mFPS))
mHours = int(mFrameCount / (60 * 60 * mFPS))

#if (bDropFrame) {
mDropOffset = (bDropFrame && mFPS==30) ? DropFrameOffset(mHours, mMinutes, mSeconds) : 0

mFrameCount = (bDropFrame && mFPS==30) ? mFrameCount + mDropOffset : mFrameCount

mFrames = mFrameCount % mFPS
mSeconds = int((mFrameCount % (60 * mFPS)) / mFPS)
mMinutes = int((mFrameCount % (60 * 60 * mFPS)) / (60 * mFPS))
mHours = int(mFrameCount / (60 * 60 * mFPS))
#}

strTimeCode = String(mHours, "%02.0f") + ":" + String(mMinutes, "%02.0f") + ":" + String(mSeconds, "%02.0f")

strTimeCode = (bDropFrame && mFPS==30) ? strTimeCode + ";" + String(mFrames, "%02.0f") \
: strTimeCode + ":" + String(mFrames, "%02.0f")

return strTimeCode
}

mFrame = 3600

BlankClip(length=30, width=720, height=480, fps=30, audio_rate=48000, stereo=true, color=$000000)

Subtitle("Timecode: " + Frames_to_TimeCode(mFrame, 30, true) + " - 30fps Drop", 0, 0 )
Subtitle("Timecode: " + Frames_to_TimeCode(mFrame, 30, false)+ " - 30fps Non-Drop" , 0, 30)
Subtitle("Timecode: " + Frames_to_TimeCode(mFrame, 25, false) + " - 25fps", 0, 60)
Subtitle("Timecode: " + Frames_to_TimeCode(mFrame, 24, false) + " - 24fps", 0, 90)


I'll look up TimeCode_to_Frames when I get home in a few hours and see if I can make a similar script.

A limitation of this script is that it only accepts integer FPS values. This should be ok, though, because 29.97 and 30fps are identical when converting from frame number to timecode.

Mug Funky
14th February 2006, 04:28
hey, thanks heaps :) i was just trying to do that now, and not getting too far... (might have got there eventually though... who knows?)

[edit]

hmm... it doesn't appear to go from xx:x0:xx:29 to xx:x1:xx:02 like it should (on all but mod10 mins). the rest works fine though, just means it drifts off real time by 2 frames a minute

tateu
14th February 2006, 06:07
Well, there is a slight rounding error in "Frames_to_TimeCode" but only in higher numbers, when you get into maybe 5-7+ hours. Otherwise, it does drop frames correctly.

Frames_to_TimeCode(1798, 30, true) returns 00:00:59;28
Frames_to_TimeCode(1799, 30, true) returns 00:00:59;29
Frames_to_TimeCode(1800, 30, true) returns 00:01:00;02
Frames_to_TimeCode(1801, 30, true) returns 00:01:00;03
Frames_to_TimeCode(17982, 30, true) returns 00:10:00;00

Here's a version that I think fixes the rounding errors and adds Timecode to Framenum conversion:

function DropFrameOffset(int mHours, int mMinutes) {
return 2 * ((mHours * 60 + mMinutes) - ((mHours * 60 + mMinutes) / 10))
}

function Frames_to_TimeCode(int mFrameCount, int mFPS, bool bDropFrame) {
mFloatFPS = (bDropFrame && mFPS==30) ? 29.97 : 30

mFrames = mFrameCount % mFPS

mSeconds = int((mFrameCount % int(60 * mFloatFPS)) / mFloatFPS)
mMinutes = int((mFrameCount % int(60 * 60 * mFloatFPS)) / (60 * mFloatFPS))
mHours = int(mFrameCount / (60 * 60 * mFloatFPS))

#if (bDropFrame) {
mDropOffset = (bDropFrame && mFPS==30) ? DropFrameOffset(mHours, mMinutes) : 0

mFrameCount = (bDropFrame && mFPS==30) ? mFrameCount + mDropOffset : mFrameCount

mFrames = mFrameCount % mFPS
mSeconds = int((mFrameCount % (60 * mFPS)) / mFPS)
mMinutes = int((mFrameCount % (60 * 60 * mFPS)) / (60 * mFPS))

mFrameCount = ( (mFrames == 0 || mFrames == 1) && (mSeconds == 0) && (mMinutes % 10 > 0) ) \
? mFrameCount - 2 : mFrameCount

mFrames = mFrameCount % mFPS
mSeconds = int((mFrameCount % (60 * mFPS)) / mFPS)
mMinutes = int((mFrameCount % (60 * 60 * mFPS)) / (60 * mFPS))
mHours = int(mFrameCount / (60 * 60 * mFPS))
#}

strTimeCode = String(mHours, "%02.0f") + ":" + String(mMinutes, "%02.0f") + ":" + String(mSeconds, "%02.0f")

strTimeCode = (bDropFrame && mFPS==30) ? strTimeCode + ";" + String(mFrames, "%02.0f") \
: strTimeCode + ":" + String(mFrames, "%02.0f")

return strTimeCode
}
function str_TimeCode_to_Frames(String strTimeCode, int mFPS, bool bDropFrame) {

mFrames = int(Value(MidStr(strTimeCode, 10, 2)))
mSeconds = int(Value(MidStr(strTimeCode, 7, 2)))
mMinutes = int(Value(MidStr(strTimeCode, 4, 2)))
mHours = int(Value(LeftStr(strTimeCode, 2)))

#if (bDropFrame) {
mDropOffset = (bDropFrame && mFPS==30) ? DropFrameOffset(mHours, mMinutes) : 0
#}

mFrameCount = \
mHours * 60 * 60 * mFPS \
+ mMinutes * 60 * mFPS \
+ mSeconds * mFPS \
+ mFrames \
- mDropOffset

return mFrameCount
}

function int_TimeCode_to_Frames(int mTimeCode, int mFPS, bool bDropFrame) {
strTimeCode = String( int( mTimeCode / 1000000 ), "%02.0f" ) \
+ ":" + String( int(mTimeCode / 10000) % 100, "%02.0f" ) \
+ ":" + String( int(mTimeCode / 100) % 100, "%02.0f" ) \
+ ":" + String( mTimeCode % 100, "%02.0f" )

return str_TimeCode_to_Frames(strTimeCode, mFPS, bDropFrame)
}

mFrame = 1800

BlankClip(length=30, width=720, height=480, fps=30, audio_rate=48000, stereo=true, color=$000000)

strTC1 = Frames_to_TimeCode(mFrame, 30, true)
strTC2 = Frames_to_TimeCode(mFrame, 30, false)
strTC3 = Frames_to_TimeCode(mFrame, 25, false)
strTC4 = Frames_to_TimeCode(mFrame, 24, false)

Subtitle("Timecode: " + strTC1 + " - 30fps Drop", 0, 0 )
Subtitle("Timecode: " + strTC2 + " - 30fps Non-Drop" , 0, 20)
Subtitle("Timecode: " + strTC3 + " - 25fps", 0, 40)
Subtitle("Timecode: " + strTC4 + " - 24fps", 0, 60)

FN1 = String(str_TimeCode_to_Frames(strTC1, 30, true))
FN2 = String(str_TimeCode_to_Frames(strTC2, 30, false))
FN3 = String(str_TimeCode_to_Frames(strTC3, 25, false))
FN4 = String(str_TimeCode_to_Frames(strTC4, 24, false))

Subtitle("FrameNum: " + FN1 + " - 30fps Drop", 0, 100 )
Subtitle("FrameNum: " + FN2 + " - 30fps Non-Drop" , 0, 120)
Subtitle("FrameNum: " + FN3 + " - 25fps", 0, 140)
Subtitle("FrameNum: " + FN4 + " - 24fps", 0, 160)

Mug Funky
14th February 2006, 06:56
seems to have the rounding error after half an hour... 00:28:59:29 goes to 00:29:00:02, 00:29:59:29 goes to 00:30:00:00 just fine, but then 00:30:59:28 goes to 00:31:00:01...

otherwise works perfectly.

tateu
14th February 2006, 08:56
Yep, I left out some more code in my translation to avisynth. Sorry about that...In the "#if (bDropFrame)" section I added two lines. My previous post has been modified with the changes.

And if you're interested, here's what I just built to try and test it:
http://www.tateu.net/software/dl.php?f=TC_Tester_7z

It's a text list of 313,500 frames and an avs script that reads it using ConditionalReader and ScriptClip(Subtitle) to overlay Frames_to_TimeCode for every frame in 30fps drop, 30fps non-drop, 25fps and 24fps and then conversion of those timecodes back into frames using str_TimeCode_to_Frames.

Mug Funky
14th February 2006, 09:18
cool. thanks. i've been testing using a little text overlay thing with your function feeding it a string. works very well. just feeds your function a "current_frame" through scriptclip, so you can play back and watch the numbers ticking.

this will help a LOT with segment re-encodes on NTSC material. not something i've had to do much of, but it's incredibly annoying tracking down the correct GOP when all spruce gives me is DF timecodes to work with... the conversion for PAL as you know is so so much easier :)

tateu
14th February 2006, 09:48
grrr, still has some rounding errors in the higher stratospheres but I think I got it licked now...

Fixed (again) version modified in the post a few messages back. It's also contained in the new http://www.tateu.net/software/dl.php?f=TC_Tester_7z which uses "current_frame" instead of ConditionalReader (thanks for that tip). I'd swear I searched all over the Avisynth documentation looking for a way to get the current frame number like that. I guess I should have checked avisynth.org too.

Yeah, I can't stand drop frame time code. My first exposure to it was 6 or so years ago. I had to put together an Excel spreadsheet (sort of like an EDL) containing 24 hours of material built from clips ranging in length from 15 seconds to 2 minutes. All of the clips were 30fps non-drop but the spreadsheet had to be drop frame. This was for playback during a 2000 New Year's celebration and so certain spots had to play at exact times. It was a nightmare to sort it all out. I ended up writing a bunch of excel macros that convert from drop to non-drop and from timecode to frames. That code forms the basis of the avisynth stuff above. And ever since then, I go into a murderous rage anytime I have to deal with drop-frame timecode (which is still every New Year's)

Mug Funky
14th February 2006, 10:20
well, the principle is good from an encoding viewpoint - it doesn't go out of sync with PAL TC, which is handy when running NTSC through a standards-converter.

the encoder card controls the deck with NTSC timecode, but receives frames in PAL, meaning if you use NDF you'll end up with an encode that stops short. with DF you can go frame-accurate (if such a thing exists with a standards-converter).

for recording/filming purposes DF is almost totally useless though.

but i digress...

hanfrunz
14th February 2006, 12:42
@tateu
i just looked on the waveform of the generated .wav file. Your code produces straight rectangels, but there should be a rise and fall time of 200ns +- 50ns for 525/60 and 625/50 televisions systems. (see SMPTE 12M for further details). I think i'll do a test with a hardware TC-Reader this afternoon. I'll report the result.

EDIT:
Okay i tested the signal. I imported a .wav-file to an Avid Mediacomposer send it through a mixing console and connected it to an old BetacamSP-Recorder. The TC-Reader can read the signal, but it stops every few seconds. I used the EQ and filtered out the high frequencies, then it worked perfectly!
So i think the next thing to do is to implement the rise and fall times, to avoid too much harmonic frequencies.

EDIT2:
tateu: do you think you can add a few more comments to your sourecode, i find it hard to read. Thanks.

regards,
hanfrunz

tateu
14th February 2006, 18:16
Yes, my coding style (do I even have a style?)...I never claimed I was any good at programming...It's just a hobby that I enjoy doing :) I'll look into adding some comments later today.

I don't have the smpte specs, but I had read about the rise and fall time on this site (http://www.philrees.co.uk/articles/timecode.htm) that I linked to in my first message. I confess that I don't really understand the rise time so I ignored it. And to add to my ignorance...are you sure you mean 200ns (nanoseconds) as in 200/1,000,000,000 because 44100*200/1,000,000,000 equals less than one audio sample. How do you do something that is smaller than one audio sample? The philrees site says a rise time of 25 +-5 microseconds but even that doesn't make much sense to me since 44100*30/1,000,000 = 1.3 samples. If it takes only one audio sample to go from full negative to full positive isn't that a square waveform?

Since I do not have the smpte specs (and probably wouldn't understand them if I did) I have been looking at the waveforms of sample timecode files that I have. 1) is a perfect square waveform (just like mine) but it was downloaded from a forum similar to this so it can't necessarily be taken as proof of the standard. 2) is the file downloaded from the philrees site and it is definately not a square waveform. Assuming that my understanding of rise time is correct (the time it takes the audio signal to go from full negative amplitude to 90% of full positive amplitude)...It appears to have a rise time of about 8/22050 ~ 363 microseconds. 3) is the Timecode out of an Avid Adrenaline into a sound card, captured at 44.1Khz. The rise and fall time of this file is between 2/44100 and 4/44100 ~ 45 - 90 microseconds.

Obviously, three test files is not enough for me to get a definative idea of the correct waveform output of timecode, but it's all I had to work with. Also, I've occasionally looked at the waveforms of various timecode files over the years. I dismissed the philrees sample right at the outset because it was so wildly different from anything I had previously looked at. I figured that the 2 to 4 audio sample rise time on the Adrenaline file was...well, I attributed it to line noise or something similar. And then the square waveform sample file fell in line perfectly with what I was hoping for because it is the easiest method to implement.

I hooked up the audio out of my Adrenaline into a Sony UVW-1800 BetaSP (NSTC) deck and it didn't have any trouble reading my timecode but I think it might have been in regen mode. If I unplugged the timecode signal, the deck's timecode kept running. I couldn't find a setting for regen in the menu, though, to turn it off. All I found was record run or free run. I have a few higher end decks with more menu options, I'll run a few tests with those today.

hanfrunz
14th February 2006, 20:41
@tateu
my idea of writing this filter was to generate the four waveforms you need in the constructor of the filter (0, 1 and both with alternate phase) and then just put these pieces together.

something like:

LTC=waveform(0)+waveform(1)+waveform(2)+waveform(3)...

when you are ready with rendering the whole frame(s) you return the part, requested.

I hope i find some time on the weekend, i could try to implement it then.
Another way could be a second filter like smoothLTC to smooth a LTC-waveform...

regards,
hanfrunz

tateu
15th February 2006, 12:26
I liked your idea of creating the 4 waveforms ahead of time so much that I implemented it in my code. I also tried to add the rise/fall time. I don't have the smpte docs, but just about every site I could find said it should be 25 Ás which is only one audio sample so that's how I tried to implement it. And I found some sites talking about a +-5% of maximum overshoot/undershoot so I tried adding that in too. This version has more comments, but I think I still need to add more of them and more detail to them.

There is a new parameter for the avisynth version:
square=0 (the default) will produce a waveform using my attempt at rise/fall and overshoot/undershoot
square=1 will produce a square waveform like the previous versions

For the cli version it's -q 0

http://www.tateu.net/software/dl.php?f=LTCGen_Avisynth
http://www.tateu.net/software/dl.php?f=LTC_Gen_cli

TactX
15th February 2006, 22:58
i like to write a simple audiogenerator: sinus waves, noise
If you want to implement a noisegenerator, you should keep in mind, that all PRNGs produce the so called "white noise", which is very sharp. You propably want "pink noise" (1/f noise). To get this you will have to do filtering on the PRNGs output. Here is a nice paper (http://www.firstpr.com.au/dsp/pink-noise/) on how you could do that.