Monday, October 31, 2011

Simple Range Compressor

I made brief mention in the previous post about a little Windows tool that I wrote.  It's called Simple Range Compressor.  You can download it from here.  There's a good reason that the word 'simple' is in the title:

The purpose of this program is to perform dynamic range compression.  What this means is that it will attempt to make all sound play at the same volume.  In terms of PseudoTV, it will try to make each show / movie play at the same volume.  It will also keep those shows / movies playing at that volume no matter what is happening on screen.  Going from dialog to explosions shouldn't make any difference.

First the why
There are two main reasons to use this.  First is equalizing volume across shows.  If you play back a few shows in a row then you know that they aren't the same volume.  If you then switch to a movie, those are generally much different.  I hate messing with the volume control all of the time...this prevents that need.

The second reason is for those that don't want to wake their roommate / wife / baby.  You can watch a movie and not have to worry that it will suddenly get extremely load, or that you turn it down and can no longer hear the dialog.  They are the same volume.

Now for the how
Windows Vista and Windows 7 changed how they did audio quite a bit from previous versions.  They allow easy access to many things that used to be driver-dependent.  Simple Range Compressor is a very small program.  I took the easy, but effective, way out.  When the program starts, it notes the volume level that you have set.  It will then try to have all sounds play at that level.  It does this by actually adjusting the volume position itself.  If you click on the volume control you can see that it will slide up and down on its own.  This is also why you don't want the volume to be set to the maximum when using this: there is no room for changing the volume of the sound...explosions are already at that maximum, and it can't increase the volume in order for you to hear dialog.

Why is it simple
There are a few things that all dynamic range compression algorithms have to deal with...threshold, attack time,  and release time.  This program deals with those concepts as well.  Thing is, I just don't think that anyone cares.  If I set a threshold to -75db, does someone really care enough to change that to -70?  If I have an attack time of 20ms, will it make a big difference if it's 15ms instead?  I've set these things to what sounds best on my videos.  Since, after finding the values that I liked, I have no desire to change them anymore, I decided that I just wouldn't make them options at all.  Start the program, minimize it to put it in the system tray, and forget about it.

I hope people find some value in this.  I've been searching for a while for something to do this sort of thing, and I think this is a good solution.  I'm asking that if you try it and like it, please donate $1.00.  Not a lot, but I'm considering it an 'app' and that seems like a reasonable price.


Friday, October 28, 2011

Version 2.0 Released

Version 2.0 is finally out the door.  Here are some of the new features:

Channel Rules

This is the big feature for this release.  You're now able to customize every channel with a set of (currently) 12 different rules.  Here is a quick rundown:

Scheduling - Want to watch The Daily Show every night at 7:00?  Schedule your channel to play it.

Don't play this channel - Since some rules use other channels as modifiers (scheduling and interleaving), you may not want to actually play your channel that only has dozens of Battlestar Galactica episodes.  Hide it with this rule.

Don't play a show on this channel - I want to watch my episodes of Alias in order, but they keep showing up on my ABC channel.  Excluding them is only a rule away.

Force Random / Real-Time / Resume - While most people use Real-Time mode (as recommended), you can now have a channel be different from the rest.

Rename a channel - Don't like the default channel name?  Rename it to "Chick Stuff" to show that only your wife wants to watch the crap that plays on this channel*.

Interleaving - Are you one of those people that likes bumpers?  Then just create a channel that has only the bumpers and use this rule on your main channel to interleave those bumpers in between your shows.

Only play unwatched shows - If the channel contents change a lot, there's no reason to watch the same shows over and over.  Hide things you've watched from this channel with ease.

Only play watched shows - Don't want to spoil a movie by accidentally seeing part of the middle?  Hide shows that haven't been watched yet.

Play shows in order - Now you can make sure that when you're watching Top Chef, the next episode that comes on will actually be the next episode in the series.

Always Keep Paused - Don't want to miss an episode of The Big Bang Theory?  Make sure that if you're not watching it, it stays paused.  This rule goes great with the "Play shows in order" and "Force resume" rules!

* this is really the channel name I have for where my wife's crappy shows play.

Channel Sharing

I'm an old man, so my bed time is 10pm.  If I'm in the middle of a show at 9:15 and want to go from downstairs to upstairs, I just turn off the TV and turn on my system in my bedroom.  The channels are shared between the two so I can keep watching where I left off.  Huzzah!

Setting this up is a simple matter of pointing each computer to the same directory in the addon settings:

Background Channel Loading

As someone without patience, I want everything to start as quickly as possible.  Can I get rid of the loading time all together?  No...sorry.  I can cut it down, though.  As long as at least one channel is ready to go, that channel will be started.  Every other channel will be updated and added to the system as soon as they are ready.  A little notification will be given to show that channels are loading:

And another is shown when a new channel is available:

Other Changes

There are some other new things going on, although they may not be as obvious.  Here's a quick rundown:

Directory-type channels are supported now.
The EPG clock can be set to use a 12-hour or 24-hour clock.
Channel creation for first-time users has been significantly improved.
Time accuracy between restarts is essentially perfect now...or close enough for government work.
The EPG should function a bit quicker now.

I hope you enjoy the changes.  There are still a couple of bugs to be worked out (IceLibrary doesn't work, yet) and a couple of features, but this version feels pretty solid to me.

As a side note, I've created a little Windows program (Vista and Windows 7 only) called the "Simple Range Compressor" that tries to maintain the same volume between shows by performing some dynamic range compression using the Windows volume slider...wacky, but it works:

Download it here, discuss it here.

Friday, September 2, 2011

Channel Scheduling

There is an update to stable-pre in the repository.  This has the standard bug fixes as should be expected for every release.  It also fixes (and slightly modifies) the interleave rule.  It's now possible to select a minimum and maximum amount for the interleaving value.  This means that the number of shows between each inserted episode will be chosen randomly between the minimum and maximum value.  If you pick 3 and 5, for example, then there will be between 3 and 5 episodes of the normal channel followed by a single episode of the inserting channel.  After that, 3 to 5 shows of the normal channel and 1 of the inserted.

It also has at least the initial version of the scheduling.  I've decided to rename this to "Best-Effort" scheduling.

Best-Effort Channel Scheduling

Using this rule you do not schedule a movie or show, you schedule one channel to be played at a certain time during another channel.  This gives all sorts of flexibility since the scheduled channel can use rules of its own.

Why is it best-effort instead of just real "play this show exactly at this time" scheduling?  Because that way would be really damn hard, if not impossible.

When you watch a normal broadcast channel, the length of everything from the shows to commercials to the black spots in between are all timed so that the final length of an episode of the Simpsons will be 30 minutes.  I don't have that control.  In PseudoTV I give most of the control to XBMC itself to play a playlist that is created.  The best that I can do is insert an item into that playlist that should play (based on the length of each show before it) at about the time you want.

Of course just inserting a show blindly at about that time would result in really shitty timing.  If a movie is normally playing between 6 and 8 and you've scheduled something for 7, what would I do?  Either way the show will be an hour off.  Ideally what I would do is to rearrange the shows so that something ends at exactly 7 so that the new show can be inserted.  As I discovered, this is classic computer science problem called the subset sum problem.  Can any of the values in this subset be added together to get to zero.  This is effectively the problem that I faced.

First attempt

My first goal was to solve this problem.  In this case, I could setup a range that was valid: anywhere between 120 seconds before or after the time slot was good enough.  I hoped that this would make it easier.  I also decided to confine my search to only combinations of up to 4 values.  So I created an array that had the sum of all values added together:

{ [ index 1: 1021, index 2: 1426, total: 2447 ], [ index 1: 1021, index 3: 709, total: 1730 ], ... , [ index 499: 639, index 500: 3285, total: 3924 ] }

On top of that, I appended all combinations of 3 values, and then all combinations of 4 values.  It would be only a matter of taking the time that I wanted and seeing if swapping the shows from before the time to after would get me to the value that I wanted.

This took quite a bit of memory.  For only 10 shows I would have a total of 45 combinations of two,  120 combinations of three, and 330 combinations of four.  This means that for only 10 shows I would have 495 array entries.  Now, computers have a lot of memory.  In reality, these 495 entries would probably take up about 6KB worth of memory, not a significant amount by any means.  As the total number of entries increases, though, the total amount of memory grows extremely quickly.  Going from 10 to 20 entries raises the memory requirements to 88KB, 30 entries takes up 431KB.

Even this isn't a huge amount.  The problem is that, on occasion, the size of a playlist will get into the thousands.  How much memory?  I honestly have no idea.  I pushed my test program up to 300 entries and before it crashes it was taking up over 1GB worth of memory.  Obviously, this approach isn't feasible.

It's worth mentioning that, yes, it could be optimized.  I could sort the entries by time and remove duplicates.  Since I can't swap the same show more than once I could eliminate duplicate indexes.  Even so, this trades memory usage for processor usage.  While I don't mind taking up some time, a script inside of XBMC should not be killing your computer...other things need to take precedence, like playing video (for instance).

Second attempt (the best-effort method)

So I still need to schedule shows, but having a script solve the subset sum problem for thousands of elements probably won't work.  What I really need is some crappy method that will work, even if it isn't ideal.

I went back to the ridiculous array I created.  With some experimentation, I realized that since the number of elements in the subset (playlist) actually allowed for a reasonable switch of two items to get my time correct. Essentially, I found that a combination of 2 items worked great in most situations.  Excellent, I can create the combinations of 2 array and just look for the closest match out of that.

This simple solution solved most of the problems.  There were situations that still didn't work properly, though.  To resolve this, I just put my function inside of a nice little loop:

for loops in range(5):
    newtime = self.rearrangeShows(showindex, lasttime, channeldata, channelList)

    if newtime == lasttime or newtime < 120:

    lasttime = newtime

This gives the system 5 chances at fixing the time, by switching up to 5 pairs of shows.  If the difference in the time between when I scheduled the show and when it will actually play is ever less than 120 seconds (plus or minus) then it will just say "good enough".

This method works pretty damn well, surprisingly.  It isn't optimal.  It does, though, minimize memory usage and attempts to get the optimal show placement out of as few processor cycles as possible.


The outcome of this algorithm, though, is heavily dependent upon the channel contents itself.  If you're scheduling a TV show on a channel that plays only movies, then it may be difficult for it to place the show very well.  There would be fewer playlist entries and since the times for each entry is so high (2 hours or so, compared to 25 minutes of a TV show) then it may be difficult to place a show when you want it.  Can I improve this algorithm?  I'm sure it could be better.  Will I?  I'm not sure it's worth while, honestly.  I think any results I get would only be incrementally better, and since the function is only a minor part of the PseudoTV package (and will probably rarely get used as it is) I don't think it's worth my time.  Hopefully everyone will be happy with the current results, though.

If you do end up with large discrepancies between when you schedule a show and when it plays, please report it!  While I probably won't change the algorithm much, I certainly will fix any bugs that show up.