|
|
|
03-07-2021, 09:46 AM
|
#1
|
Human being with feelings
Join Date: Apr 2011
Posts: 3,451
|
Split and move sibilance to child track
This test script cuts the sibilance from a vocal track and places it to a child track.
It works quite good but it could be improved. I have used the GetMediaItemTake_Peaks API. The problem with my implementation is that it cannot tell apart sibilance from breath sounds. Do you have any ideas on how I could tell them apart? Possibly by using FFT.. But again how to tell them apart? Would I need to train the algorithm with machine learning to distinguish sibilance from breaths? Or is there any other way?
|
|
|
03-07-2021, 12:34 PM
|
#2
|
Human being with feelings
Join Date: Jan 2012
Posts: 1,180
|
Hi amagalma
Surely there's a massive difference in level between a sibilant "s" and a breath. How about starting with a threshold level before moving?
|
|
|
03-07-2021, 12:56 PM
|
#3
|
Human being with feelings
Join Date: Jun 2020
Posts: 656
|
I don't really know anything about programming or anything buuut I remembered Chris from Airwindows talking about how his De-esser or De-bess works by watching over the transients of theses "esses"
Maybe look into that?
https://youtu.be/J_xT9RWz_Y8?t=235
|
|
|
03-07-2021, 02:26 PM
|
#4
|
Human being with feelings
Join Date: Apr 2011
Posts: 3,451
|
Thanks guys!
The threshold idea is something that can be easily added to my existing code. Going to try it
|
|
|
03-08-2021, 04:08 AM
|
#5
|
Human being with feelings
Join Date: Apr 2011
Posts: 3,451
|
Here is the link to the script, if anyone wants to give it a try:
amagalma_Split selected items and move sibilance to child track (semi-automatic de-essing).lua
I am sure this is not the best way to do it but it works 95% of the time here..
I have made various choices here, which probably if fine-tuned could give better results?:
- a sound can be considered as a sibilant if its pitch is above 1850Hz or it's tonality is below 15%
- if it meets the previous requirements then it must be above the threshold, which is 14dB below the RMS of the item. If it is not, then it is a breath and not a sibilant
- sibilant sounds must be at least 50ms long
- sounds between two sibilants must be at least 50ms long, otherwise the two sibilants are merged into a bigger one
|
|
|
03-08-2021, 04:19 AM
|
#6
|
Human being with feelings
Join Date: Jul 2009
Posts: 1,231
|
Wow! This has great potential. Will testdrive as soon as I get the opportunity.
|
|
|
03-08-2021, 07:11 AM
|
#7
|
Human being with feelings
Join Date: Apr 2020
Posts: 214
|
This is a really cool idea amagalma! I would think it could be very useful to optionally split the breaths off to a second child track; often it can be good to control their volumes separately.
|
|
|
03-08-2021, 01:07 PM
|
#8
|
Human being with feelings
Join Date: Jan 2007
Location: mcr:uk
Posts: 3,889
|
Neat!
|
|
|
03-08-2021, 09:12 PM
|
#9
|
Human being with feelings
Join Date: Nov 2017
Location: Uruguay
Posts: 10
|
Thanks amagalma!
This is super useful and has enormous potential.
I adhere to the idea of an optional second track for breaths
I would maybe add auto overlapping fadeout-fadein between splits to make transitions softer... kind of crossfades between items in separate tracks
|
|
|
03-09-2021, 12:24 AM
|
#10
|
Human being with feelings
Join Date: Apr 2020
Posts: 1,501
|
I get:
... sibilance to child track (semi-automatic de-essing).lua:52: number (local 'spl') has no integer representation
What am I doing wrong?
Edit: Oh yeah, Spectral Peaks need to be on! Sorry!
|
|
|
03-09-2021, 11:21 PM
|
#11
|
Human being with feelings
Join Date: Sep 2018
Location: China
Posts: 565
|
Hi. I got this error message:
Code:
.lua:52: number (local 'spl') has no integer representation
|
|
|
03-10-2021, 08:02 AM
|
#12
|
Human being with feelings
Join Date: Apr 2020
Posts: 1,501
|
My post above yours!
|
|
|
03-10-2021, 10:00 AM
|
#13
|
Human being with feelings
Join Date: Sep 2018
Location: China
Posts: 565
|
Quote:
Originally Posted by Pink Wool
My post above yours!
|
Oh I see, thanks!
|
|
|
05-04-2022, 05:09 AM
|
#14
|
Human being with feelings
Join Date: Sep 2010
Posts: 170
|
This has been what I've been looking for! A laborious task made simple and easy.
Keep developing it. Cheers!
|
|
|
05-08-2022, 01:30 AM
|
#15
|
Human being with feelings
Join Date: May 2020
Posts: 5
|
Quote:
Originally Posted by amagalma
I have made various choices here, which probably if fine-tuned could give better results?:
- a sound can be considered as a sibilant if its pitch is above 1850Hz or it's tonality is below 15%
- if it meets the previous requirements then it must be above the threshold, which is 14dB below the RMS of the item. If it is not, then it is a breath and not a sibilant
- sibilant sounds must be at least 50ms long
- sounds between two sibilants must be at least 50ms long, otherwise the two sibilants are merged into a bigger one
|
Just FYI, I made a JS de-esser which detected esses by comparing comparing the level of the signal low-passed at 800Hz with the level of the signal high-passed at 5000 Hz. If the high-passed level (multiplied by a sensitivity factor) is greater than the low passed level, I consider it an ess.
IIRC breaths tend to have more energy on the lower frequencies then esses, so they tend not to be mistaken for esses.
|
|
|
05-08-2022, 11:44 AM
|
#16
|
Human being with feelings
Join Date: Jul 2007
Location: Jazz City
Posts: 5,065
|
Quote:
Originally Posted by Iain_mf
Just FYI, I made a JS de-esser which detected esses by comparing comparing the level of the signal low-passed at 800Hz with the level of the signal high-passed at 5000 Hz. If the high-passed level (multiplied by a sensitivity factor) is greater than the low passed level, I consider it an ess.
IIRC breaths tend to have more energy on the lower frequencies then esses, so they tend not to be mistaken for esses.
|
What an amazing idea! Love it – when is it going to be released??
__________________
Windows 10x64 | AMD Ryzen 3700X | ATI FirePro 2100 | Marian Seraph AD2, 4.3.8 | Yamaha Steinberg MR816x
"If I can hear well, then everything I do is right" (Allen Sides)
|
|
|
05-14-2022, 06:38 PM
|
#17
|
Human being with feelings
Join Date: May 2020
Posts: 5
|
Quote:
Originally Posted by beingmf
What an amazing idea! Love it – when is it going to be released??
|
I do not intend to release it, but here it is, provided as is, at your own risk, in the public domain.
Code:
desc:iain De-ess mono 2
// this is in the public domain
import cookdsp.jsfx-inc
slider1:3<0,10,0.01>sensitivity
slider2:0<-48,12,1>ess level
slider3:0<-48,0,1>ess limit
slider4:0<0,1,1{Off,On}>Monitor
slider5:0<-12,12,1>boost
slider6:1000<1000,10000,1>ess lo cut
in_pin:left input
in_pin:right input
out_pin:left output
out_pin:right output
//out_pin:lo graph
//out_pin:high graph
@init
eq.eq(5000, 1, 0, 0);
// Initializes the filters
lo_band.butlp(800);
lo_band.butlp_set_freq(800);
hi_band.buthp(5000);
hi_band.buthp_set_freq(5000);
lp1.butlp(20);
lp2.butlp(40);
hp.buthp(slider6);
lo_level=20*log10(0.0001);
hi_level=20*log10(0.0001);
sens=slider1;
ess_gain=2 ^ (slider2/6);
ess_limit=2 ^ (slider3/6);
ess_gr=1;
monitor=slider4;
echolength = 10 * (srate / 1000) ;
// peak hold
last=0;
cross_pos_count=0;
cross_neg_count=0;
last1=0;
cross_pos_count1=0;
cross_neg_count1=0;
cycles=3;
@slider
sens=slider1;
ess_gain=2 ^ (slider2/6);
ess_limit=2 ^ (slider3/6);
monitor=slider4;
eq.eq_set_boost(slider5);
pdc_delay=10 * (srate / 1000) ;
pdc_bot_ch=0; pdc_top_ch=2;
hp.buthp_set_freq(slider6);
@block
@sample
in = (spl0+spl1)*0.5;
lo_band=lo_band.butlp_do(in);
hi_band=hi_band.buthp_do(in);
// lo band peaks envelope
in_pos=lo_band;
in_neg=lo_band*-1;
out_pos=max(in_pos,last_out_pos);
cross_pos=sign(in_pos)-sign(last_in_pos);
out_neg=max(in_neg,last_out_neg);
cross_neg=sign(in_neg)-sign(last_in_neg);
peaks=max(out_pos,out_neg);
lo_level = peaks;
cross_pos > 0 ? (cross_pos_count += 1;);
cross_pos_count > cycles ?
(
cross_pos_count=0;
last_out_pos=0;
): (
last_out_pos=out_pos;
);
last_in_pos=in_pos;
cross_neg > 0 ? (cross_neg_count += 1;);
cross_neg_count > cycles ?
(
cross_neg_count=0;
last_out_neg=0;
): (
last_out_neg=out_neg;
);
last_in_neg=in_neg;
/////////
//hi band peak envelope
in_pos1=hi_band;
in_neg1=hi_band*-1;
out_pos1=max(in_pos1,last_out_pos1);
cross_pos1=sign(in_pos1)-sign(last_in_pos1);
out_neg1=max(in_neg1,last_out_neg1);
cross_neg1=sign(in_neg1)-sign(last_in_neg1);
peaks1=max(out_pos1,out_neg1);
hi_level = peaks1;
cross_pos1 > 0 ? (cross_pos_count1 += 1;);
cross_pos_count1 > cycles ?
(
cross_pos_count1=0;
last_out_pos1=0;
): (
last_out_pos1=out_pos1;
);
last_in_pos1=in_pos1;
cross_neg1 > 0 ? (cross_neg_count1 += 1;);
cross_neg_count1 > cycles ?
(
cross_neg_count1=0;
last_out_neg1=0;
): (
last_out_neg1=out_neg1;
);
last_in_neg1=in_neg1;
// smooth peak envelopes
lo_level=lp1.butlp_do(lo_level);
hi_level=lp2.butlp_do(hi_level*sens);
hi_level > lo_level ? (is_ess=1;):(is_ess=0.0 ;);
hi_level > ess_limit ? (ess_gr=ess_limit/hi_level;):(ess_gr=1;);
bufpos[0] = in ;
bufpos = bufpos + 1 ;
bufpos > echolength ? bufpos = 0;
delayed = bufpos[0] ;
sign_in=sign(delayed);
sign_in != last_sign_in ? (out_ess=is_ess;)
:(out_ess=last_out_ess;);
last_out_ess=out_ess;
last_sign_in=sign_in;
// with monitoring on, esses are panned left and everything else panned right
monitor == 1 ? (
outl= hp.buthp_do(delayed*( out_ess*ess_gain*ess_gr));
outr=+delayed*(1- out_ess);
):(
outl =outr = hp.buthp_do(delayed*( out_ess*ess_gain*ess_gr))+delayed*(1- out_ess);
outl = outr = eq.eq_do(outl);
) ;
spl0 = outl;
spl1 = outr;
//spl2 = out_ess ;
//spl3 = hi_level ;
@gfx
meter_scale=25;
zero_line=(3)*gfx_h/meter_scale;
ess_gr > last_ess_gr ? (
ess_gr=(ess_gr*0.05)+(last_ess_gr*0.95);
);
last_ess_gr=ess_gr;
gfx_r=out_ess*0.5;
gfx_g=(1-out_ess)*0.5;
gfx_b=0;
gfx_a=1;
lo_level=((20*log10(ess_gr)*-1)+3)*gfx_h/meter_scale;
gfx_y=0;
gfx_x=0;
gfx_rectto(gfx_w,lo_level);
gfx_r=1;
gfx_g=1;
gfx_b=1;
gfx_a=0.5;
zero_db=(3)*gfx_h/meter_scale;
gfx_line(0,zero_db,gfx_w,zero_db) ;
six_db=(6+3)*gfx_h/meter_scale;
gfx_line(0,six_db,gfx_w,six_db);
twelve_db=(12+3)*gfx_h/meter_scale;
gfx_line(0,twelve_db,gfx_w,twelve_db) ;
eighteen_db=(18+3)*gfx_h/meter_scale;
gfx_line(0,eighteen_db,gfx_w,eighteen_db) ;
gfx_x=10;
gfx_y=zero_db-10;
gfx_drawnumber(0,1);
gfx_x=10;
gfx_y=six_db-10;
gfx_drawnumber(-6,1);
gfx_x=10;
gfx_y=twelve_db-10;
gfx_drawnumber(-12,1);
|
|
|
05-18-2022, 02:46 PM
|
#18
|
Human being with feelings
Join Date: Dec 2019
Posts: 588
|
it does the analyzing but doesn't split and create children track. any idea why ?
|
|
|
05-18-2022, 10:31 PM
|
#19
|
Human being with feelings
Join Date: Apr 2020
Posts: 1,501
|
Quote:
Originally Posted by permeke
it does the analyzing but doesn't split and create children track. any idea why ?
|
Do you have Spectral Peaks on?
|
|
|
05-19-2022, 02:00 AM
|
#20
|
Human being with feelings
Join Date: Dec 2019
Posts: 588
|
yes I do
|
|
|
05-19-2022, 10:52 PM
|
#21
|
Human being with feelings
Join Date: Apr 2020
Posts: 1,501
|
Quote:
Originally Posted by permeke
yes I do
|
Sorry to ask but does the item have hard S's?
|
|
|
05-20-2022, 12:47 AM
|
#22
|
Human being with feelings
Join Date: Dec 2019
Posts: 588
|
Quote:
Originally Posted by Pink Wool
Sorry to ask but does the item have hard S's?
|
aaaah, you are correct. S's were too soft.
But I get the idea now.
thanks
|
|
|
05-20-2022, 04:08 AM
|
#23
|
Human being with feelings
Join Date: Apr 2020
Posts: 1,501
|
Quote:
Originally Posted by permeke
aaaah, you are correct. S's were too soft.
But I get the idea now.
thanks
|
Nice!
|
|
|
05-20-2022, 06:46 PM
|
#24
|
Human being with feelings
Join Date: Jun 2021
Location: Moscow, Russia
Posts: 280
|
Sorry if a bit off-top. Amagalma, is it very hard to make script like this that can detect whole man’s speech and split it from other ambience noise or cloth rustle? Recent Nuendo 12 can do it. Very handy feature for post production.
|
|
|
05-21-2022, 12:02 AM
|
#25
|
Human being with feelings
Join Date: Apr 2011
Posts: 3,451
|
That will require some well-trained AI algorithm, which is way beyond my skills and free time.
|
|
|
05-23-2022, 04:39 AM
|
#26
|
Human being with feelings
Join Date: Jun 2021
Location: Moscow, Russia
Posts: 280
|
Ok, good to know Thank for answering!
|
|
|
12-18-2023, 11:02 PM
|
#27
|
Human being with feelings
Join Date: Sep 2022
Posts: 222
|
@amagalma
Can this be modified to use fixed lanes as seen here?:
https://www.youtube.com/watch?v=q5mECQ_v6oU
Instead of splitting out regions, a duplicate audio item would be created in which the regions are comp areas.
|
|
|
02-19-2024, 02:22 PM
|
#28
|
Human being with feelings
Join Date: Nov 2009
Posts: 2,227
|
apologies in advance here. i've modded this script. apologies as i'm terrible at scripting and i know this can be done better. hopefully someone will fix what i've done.
lately i've run into some issues with sibilants and, since i work more with items and takes than with tracks, i wanted a means to split sibilants and adjust them. that's what i've done here.
i've left the original code in tact but have added a couple of bits near the end, in the loop that creates the child track and moves the sibilants to that child track. i commented the track creation and move so that the split sibilants could remain in place. then i added a couple of bits to either adjust the item volume of the split item or to adjust the active take volume.
i think the way it is in the code i'm posting is that it's my template so all three options: move, item adjust, and take adjust, are comented. the appropriate sections need to be uncommented depending on how you want to save the script.
hope it's ok, amagalma! also, i swiped a bit of x-raym code from a template.
thanks,
babag
here's the modded code:
Code:
local item_cnt = reaper.CountSelectedMediaItems( 0 )
if item_cnt == 0 then return end
local items, it = {}, 0
local track = reaper.GetMediaItemTrack( reaper.GetSelectedMediaItem( 0, 0 ))
for i = 0, item_cnt-1 do
local item = reaper.GetSelectedMediaItem( 0, i )
if reaper.GetMediaItemTrack( item ) ~= track then
reaper.MB("All selected items must be on the same track", "Aborting...", 0)
return
end
local take = reaper.GetActiveTake( item )
if take and not reaper.TakeIsMIDI( take ) then
it = it + 1
items[it] = {item, take}
end
end
if it == 0 then return end
for i = 1, it do
local item = items[i][1]
local take = items[i][2]
-- sibilants 14dB below rms will be discarded
local threshold = 10^((reaper.NF_GetMediaItemAverageRMS( item ) - 14)/20)
local item_pos = reaper.GetMediaItemInfo_Value( item, "D_POSITION" )
local item_len = reaper.GetMediaItemInfo_Value( item, "D_LENGTH" )
local item_end = item_pos + item_len
local source = reaper.GetMediaItemTake_Source( take )
local nch = reaper.GetMediaSourceNumChannels( source )
local samplerate = reaper.GetMediaSourceSampleRate( source )
local ns = 1024
local buf = reaper.new_array( ns * 3 * nch )
local accessor = reaper.CreateTakeAudioAccessor( take )
local acc_buf_sz = ns * nch
local acc_buf = reaper.new_array( acc_buf_sz )
-- window length will be half of the ns block (so that windows overlap 50%)
local window = (ns / samplerate) / 2
-- Calculate cuts
local cuts, c = {}, 0
local pos = item_pos
while pos < item_end do
local rv = reaper.GetMediaItemTake_Peaks( take, 1000, pos, nch, ns, 115, buf )
if rv & ( 1 << 24 ) and ( rv & 0xfffff ) > 0 then
local spl = buf[nch*ns*2 + 1]
local pitch = spl & 0x7fff
local tonality = ( spl >> 15 ) / 16384
-- conditions to consider a split:
if pitch > 1850 or tonality < 0.15 then
-- RMS must be above threshold
local ok = reaper.GetAudioAccessorSamples( accessor, samplerate, nch, pos - item_pos , ns, acc_buf )
if ok == 1 then
local rms = 0
for i = 1, acc_buf_sz do
rms = rms + acc_buf[i]*acc_buf[i]
end
rms = (rms/acc_buf_sz)^(1/2)
if rms >= threshold then
if not cut_start then cut_start = pos - 0.01 end -- (0.01: cut a bit earlier)
end
end
else
-- if the resulting item is 50+ ms, register the splits
if cut_start and pos - cut_start >= 0.05 then
c = c + 2
cuts[c-1] = cut_start
cuts[c] = pos
end
cut_start = nil
end
end
pos = pos + window
end
reaper.DestroyAudioAccessor( accessor )
-- Store info to table
items[i][2] = c
items[i][3] = cuts
end
-- Split items
reaper.Undo_BeginBlock()
reaper.PreventUIRefresh( 1 )
-- Enable autocrossfade
local auto_xfade = reaper.GetToggleCommandState( 40912 ) == 1
if not auto_xfade then
reaper.Main_OnCommand(40927, 0) -- Enable auto-crossfade on split
end
reaper.SelectAllMediaItems( 0, false )
local split_items, s = {}, 0
for i = 1, it do
local c = items[i][2]
if c ~= 0 then
local cuts = items[i][3]
local item = items[i][1]
local splt = 0 -- split counter, makes a new item every two splits
local next_item = item -- initialize item to split
local i = 1 -- index for cuts table
while i <= c do
local even = splt % 2 == 0
-- after having done the first split:
-- if the next split item is too close ( less than 50ms ) then skip the split (merge the splits)
if even == false and cuts[i+1] and cuts[i+1] - cuts[i] <= 0.05 then
i = i + 2
-- continue merging splits if needed
while true do
if cuts[i+1] and cuts[i+1] - cuts[i] <= 0.05 then
i = i + 2
else
break
end
end
end
next_item = reaper.SplitMediaItem( next_item, cuts[i] )
-- every two splits (even splt) store the resulting sibilance item
if even then
s = s + 1
split_items[s] = next_item
end
splt = splt + 1
i = i + 1
end
end
end
if s ~= 0 then
--[[ Uncomment if moving sibilance to child track instead of changing item/take volume
-- Create child track
local track_id = reaper.CSurf_TrackToID( track, false )
local folder_depth = reaper.GetMediaTrackInfo_Value( track, "I_FOLDERDEPTH" )
reaper.SetMediaTrackInfo_Value( track, "I_FOLDERDEPTH", folder_depth + 1 )
reaper.InsertTrackAtIndex( track_id, true )
local new_track = reaper.CSurf_TrackFromID( track_id + 1, false )
reaper.GetSetMediaTrackInfo_String( new_track, "P_NAME", "sibilance", true )
reaper.SetMediaTrackInfo_Value( new_track, "I_FOLDERDEPTH", folder_depth - 1 )
-- Move sibilance items to child track
for i = 1, s do
reaper.MoveMediaItemToTrack( split_items[i], new_track )
end
--]]
--[[ Uncomment if not moving to child track but changing item/take volume instead
-- Lower sibilance items by 09dB
local dB = -09.0 -- store -09.0 as new dB value to set item or take to
local gain = 10^(dB/20) -- convert dB value to something reaper can use
local take = reaper.GetActiveTake(split_items[s]) -- Get the active take
--]]
for i = 1, s do
-- reaper.MoveMediaItemToTrack( split_items[i], new_track ) -- Uncomment to move sibilance to new track
-- reaper.SetMediaItemInfo_Value( split_items[i], 'D_VOL', gain ) -- Uncomment to set sibilance to dB value in selected items
-- take = reaper.GetActiveTake(split_items[i]) -- Get the active take -- Uncomment to set sibilance to dB value in active take in selected items
--[[ Uncomment to set sibilance to dB value in active take instead of items
if take ~= nil then -- if ==, it will work on "empty"/text items only
-- MODIFY INFOS
value_set = gain -- Prepare value output
-- SET INFOS
reaper.SetMediaItemTakeInfo_Value(take, "D_VOL", value_set) -- Set the value to the parameter
end -- ENDIF active take
--]]
end
end
-- Restore autoxfade
if not auto_xfade then
reaper.Main_OnCommand(40928, 0) -- Disable auto-crossfade on split
end
reaper.PreventUIRefresh( -1 )
reaper.UpdateArrange()
reaper.Undo_EndBlock( "Split & move sibilance to child track", 4 )
Last edited by babag; 02-19-2024 at 02:28 PM.
|
|
|
02-20-2024, 10:22 PM
|
#29
|
Human being with feelings
Join Date: Nov 2009
Posts: 2,227
|
hmm. when i tested this it worked but now i'm getting an error at line 52, part of the original code, that 'spl' has no integer representation. anybody know what that's about?
thanks,
babag
|
|
|
02-20-2024, 11:40 PM
|
#30
|
Human being with feelings
Join Date: Dec 2017
Location: Sunny Siberian Islands
Posts: 957
|
Quote:
Originally Posted by babag
hmm. when i tested this it worked but now i'm getting an error at line 52, part of the original code, that 'spl' has no integer representation. anybody know what that's about?
thanks,
babag
|
This is written above. Items must be in spectral peak display mode.
|
|
|
02-21-2024, 12:17 AM
|
#31
|
Human being with feelings
Join Date: Nov 2009
Posts: 2,227
|
funnily enough, though, when i tested, it worked well without being in spectral peaks mode.
some further playing, both with the code i modded as well as the original seems to reveal some possible memory issues? not sure if that's what it is. i have 32gb of memory but, when i try the original script on a longer track, one of about 6+ minutes with many items, i can get through about four minutes without erroring. after that, i have to progressively apply the script to smaller and smaller sections of the track in order to get through it, something like: 4 min, 45 sec, 15 sec, etc. restarting reaper in between doesn't seem to help with it. thought it might clear out some memory or something but it seems to have little effect.
eventually i get down to where i have to apply it to single, short items.
i had high hopes for this but, in my workflow, it's not working out to be very practical. hopefully somebody smarter than me can take it on. it's a great idea.
thx,
babag
|
|
|
03-11-2024, 12:46 AM
|
#32
|
Human being with feelings
Join Date: May 2021
Location: Athens
Posts: 26
|
Would it be possible to target only breath sounds? Like, big honking gasps that can defeat any amount of downward expansion / gating,
|
|
|
Thread Tools |
|
Display Modes |
Linear Mode
|
Posting Rules
|
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts
HTML code is Off
|
|
|
All times are GMT -7. The time now is 07:05 PM.
|