/** VCRPlus, a test applet for the VCR+ Encoder class. Copyright (C) 1996-2000 Doyle B. Myers This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA As a special exception, Doyle B. Myers gives permission to link this program with The Java platform and distribute the resulting executable, without including the source code for The Java platform in the source distribution. The phrase "The Java platform" specifically means the contents of all java.* packages as provided by Sun for either Commercial Use or under the Sun Community Source Code License. */ /** VCR Plus+, and PlusCode are trademarks of Gemstar Development Corporation. * These trademarks are used here only to refer to the VCR Plus+ device and * codes, and are not intended to imply that this software is approved of * in any way by Gemstar Development Corporation. The author is not affiliated * with Gemstar Development Corporation. This software is only an example * of a Java applet, and was just done for fun, adapted from information * widely available on the net (e.g., Usenet newsgroups alt.source and rec.video). * * ------------------------------------------------------------------------------------ * VCRPlus.java 1.0, November 1996 * VCRPlus.java 1.1, April 2000, cleanup, Y2K - doyle@wrq.com * * Authors: Doyle B. Myers * * from C code by: DaviD W. Sanderson * Kent Anthony Behrends * Paul Balyoz * (Original C-code author is unknown) * * Description: * The VCRPlus class is just a simple UI class that runs Choices to get parameters * for the Encoder class. It uses simple AWT Choice objects to get the date, time, * channel and duration, then makes an Encoder object with the specified parameters. * The Encoder.getPlusCode() method returns the PlusCode, and the VCRPlus class * displays it in a textField. There is a second textField that gets the Java Date * corresponding to the Choice settings just so you can see what happens when you * stuff the parameters into a Date object (it also shows the day of the week, which * is a nice way to confirm that you picked the right day). * * See below for a description of the Encoder class. * ------------------------------------------------------------------------------------ */ import java.util.Date; import java.awt.*; import java.applet.Applet; public class VCRPlus extends Applet { private static final String[] sNameA = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; // Privates for the VCRPlus class private int mYear; // specified start time, channel, duration private int mMonth; private int mDay; private int mTime; private int mDuration; private int mChannel; private Choice mYearChoice; // for event handling, the Choice objects private Choice mMonthChoice; private Choice mDateChoice; private Choice mTimeChoice; private Choice mDurationChoice; private Choice mChannelChoice; private Label mCodeField = new Label("??????"); // where we write the PlusCode private Label mDayField = new Label("???"); // where we write the Date string /** This applet method will return information about the applet when a browser * requests it. It's a polite thing to provide since it makes the Applet Info * feature work in many browsers. */ public String getAppletInfo() { return "PlusCode encoder 1.1\nby Doyle Myers "; } /** Like getAppletInfo, browsers can ask the applet about its parameters. This is * obviously a good thing to volunteer if you support any parameters in the * applet tag. In fact, this is also the only documentation for the applet * parameters in the class, so if you want to know what we can use, look here. * * If you omit any parameter(s), we'll try to initialize it to the next half * hour of the current day. Anything not specified will default to current day * and time, channel 1, 30 minute duration. */ public String[][] getParameterInfo() { String[][] theInfo = { {"start_year", "integer", "starting time year, 4-digit (e.g., 2000)"}, {"start_month", "integer", "starting time month, 1-12"}, {"start_date", "integer", "starting time day of month, 1-31"}, {"start_time", "integer", "24-hour starting time, 0000-2330 (even 30s)"}, {"duration", "integer", "recording duration in minutes, 30-300 (even 30s)"}, {"channel", "integer", "channel to record, in minutes, 1-64"} }; return theInfo; } /** Called by the VM when the applet is initialized (once at applet startup). * * Read the named parameters (see getParameterInfo() for descriptions), set the * initial values and encode the initial date. This makes the applet come up with * a code of some sort - either a specified one from the parameters, or a * reasonable guess at an interesting time. */ public void init() { super.init(); // always initialize the superclass Date theInitialDate = new Date(); // use current date mYear = theInitialDate.getYear(); mMonth = theInitialDate.getMonth() + 1; mDay = theInitialDate.getDate(); mTime = theInitialDate.getHours() * 100; mTime += theInitialDate.getMinutes(); try // try to read the parameters { // sepcified in the applet tag mYear = getIntParam( "start_year", mYear ); if( mYear >= 1900 ) { // mYear is Date offset (from 1900) mYear -= 1900; } mMonth = getIntParam( "start_month", mMonth ); mDay = getIntParam( "start_date", mDay ); mTime = getIntParam( "start_time", mTime ); // If we got all the date params, // we'll skip the catch block. // This will leave mYear, mYear, // mMonth, mDay, and mTime set to the // start time specified in the // applet parameters from the browser } catch( NumberFormatException e ) { // failed to read some param mYear = theInitialDate.getYear(); mMonth = theInitialDate.getMonth() + 1; mDay = theInitialDate.getDate(); mTime = theInitialDate.getHours() * 100; // mYear, mMonth, mDay are now set to today } // we now have a start time (either specified by applet params, or now) // ensure that the start time is on a half-hour int theMinutes = mTime % 100; if( theMinutes > 30 ) { mTime += 100; // round up to next hour mTime -= mTime % 100; // even hour } else if( theMinutes > 0 ) { mTime -= mTime % 100; // previous even hour mTime += 30; // plus a half-hour } if( mTime > 2330 ) { // after 2330? mTime = 0; // round up to midnight } // mTime is now the next even half-hour after now (up to 11:30 PM) // mTime is always of the format 'hhmm', e.g., 1200 for noon, // 1300 for 1PM, or 1430 for 2:30 PM. try { mDuration = getIntParam( "duration", 30 ); // try to read the duration } catch( NumberFormatException e ) { // if unable to get duration, mDuration = 30; // default to 30 minutes } try { // same deal, try to get channel parameter mChannel = getIntParam( "channel", 1 ); } catch( NumberFormatException e ) { mChannel = 1; // default to 1 if unable to read it } // we how have an initial start time, channel and duration // we use a really simple n-by-2 grid layout -- label: [field] // label: [field] // label: [field] setLayout( new GridLayout( 0, 2 ) ); // 2 columns, unspecified # rows, we'll just add 'em // now populate the layout with a bunch of "label: [field]" pairs // we make a Date just 'cause it's easy to pass to the addXxxCaption members Date specifiedDate = new Date( mYear, // year mMonth - 1, // month (java.util.Date is 0-11, we're 1-12) mDay, // day (1-31) mTime / 100, // hours mTime % 100 ); // minutes (0 or 30) addDayCaption( specifiedDate ); addMonthChoice( specifiedDate ); addDateChoice( specifiedDate ); addYearChoice( specifiedDate ); addTimeChoice( specifiedDate ); addChannelChoice( mChannel ); addDurationChoice( mDuration ); addPlusCodeCaption(); // the elements have now all been added to the applet frame try { // we now make an Encoder object with the specified parameters // making the object actually generates the PlusCode Encoder theEncoder = new Encoder( mYear % 100, mMonth, mDay, mTime, mDuration, mChannel ); // we now just need to fetch the PlusCode and put it in the // 'mCodeField' field so we can display it. mCodeField.setText( Integer.toString( theEncoder.getPlusCode() ) ); // the two lines of code inside this 'try' block are all there are to // using the Encoder class. You just construct one, then call getPlusCode(). } catch( Exception e ) { // the Encoder class throws Exception if there is a problem // we don't do anything with it, so the 'mCodeField' will // not contain a valie value. If this happens, we put '??????' // in the field just so we don't leave bogus info there. mCodeField.setText( "??????" ); } } /** Parse a time string, with or without colon (hh:mm and hhmm same result) * @returns int representation of string (sans colon). */ private int parseTime( String inString ) { int theColon = inString.indexOf( ':' ); if( theColon >= 0 ) { inString = inString.substring( 0, theColon ) + inString.substring( theColon + 1 ); } return Integer.parseInt( inString ); } /** Inverse of parseTime, turns a string like "0200" into "02:00" * Assumes a 4-digit inString. */ private String makeTime( String inString ) { if( inString.length() != 4 ) { System.out.println( "VCRPlus.makeTime: inString wrong length (" + inString.length() + ")" ); } if( inString.indexOf( ':' ) >= 0 ) { return inString; // already had a colon } else { return inString.substring( 0, 2 ) + ":" + inString.substring( 2 ); } } /** Standard applet event handling. We only care when one of our Choice objects * changes its value, so we just need to look for inEvent.id == ACTION_EVENT and * pass everything else up to the superclass to handle. */ public boolean /** @return 'true' if we handled the event completely */ handleEvent( Event inEvent ) { switch( inEvent.id ) { case Event.ACTION_EVENT: // Choice changed value if (inEvent.target == mYearChoice) // year choice changed? { // grab new value and turn it into int mYear = Integer.parseInt( mYearChoice.getSelectedItem() ) - 1900; // same deal for all the other Choice fields } else if( inEvent.target == mMonthChoice ) { //mMonth = Integer.parseInt( mMonthChoice.getSelectedItem() ); mMonth = mMonthChoice.getSelectedIndex() + 1; } else if( inEvent.target == mDateChoice ) { mDay = Integer.parseInt( mDateChoice.getSelectedItem() ); } else if( inEvent.target == mTimeChoice ) { mTime = parseTime( mTimeChoice.getSelectedItem() ); } else if( inEvent.target == mDurationChoice ) { mDuration = Integer.parseInt( mDurationChoice.getSelectedItem() ); } else if( inEvent.target == mChannelChoice ) { mChannel = Integer.parseInt( mChannelChoice.getSelectedItem() ); } // our new value is now set in whatever field changed // we just make a new Encoder with these values and // get the new value, exactly as we did in the init() method. try { Encoder theEncoder = new Encoder( mYear % 100, mMonth, mDay, mTime, mDuration, mChannel ); mCodeField.setText( Integer.toString( theEncoder.getPlusCode() ) ); Date theDate = new Date( mYear, mMonth - 1, mDay, mTime / 100, mTime % 100 ); mDayField.setText( theDate.toString() ); } catch( Exception e ) { mCodeField.setText( "??????" ); // Encoder error } return false; // let the superclass see the event, too default: return super.handleEvent(inEvent); // give everything else to the superclass } } /** All the addXxxxChoice methods work similarly. The idea is to add a static * label for the field ('Year:' for this method), then add a Java Choice field * for the range we want in the Choice field. * * There are a few details that vary between the addXxxxChoice methods, and those * are in the comments in the methods. * * Years are added as 4-digits. Choice must get biased back down to relative * to 1900, since the old Date class is relative to 1900 (e.g., 2001 is 101). */ private void addYearChoice( Date inDate ) { add( new Label( "Year:", Label.RIGHT ) ); // add the static label Choice theChoice = new Choice(); // make the (empty) Choice object int theYear = inDate.getYear(); if( theYear < 1900 ) { theYear += 1900; } for( int i = -1; i <= 1; i++ ) // years range from previous year to next year { theChoice.addItem( Integer.toString( ( theYear + i ) ) ); } // the Choice is now populated, we just need to select the specified value theChoice.select( Integer.toString( theYear ) ); // select a Choice value // takes a String // we could use 'mYear', but I thought // a Date argument was more flexible add( theChoice ); // add the populated Choice to the frame mYearChoice = theChoice; // remember this Choice for event handling } /** Nothing tricky, make a label, make a Choice with months, select the specified * month, make sure mMonthChoice matches what the user sees. Similar to * addYearChoice, but simpler since we don't care about padding, and we always * go from 1 to 12. */ private void addMonthChoice( Date inDate ) { add( new Label( "Month:", Label.RIGHT ) ); Choice theChoice = new Choice(); for( int i = 0; i < 12; i++ ) { theChoice.addItem( sNameA[ i ] ); } theChoice.select( sNameA[ inDate.getMonth() ] ); add( theChoice ); mMonthChoice = theChoice; } /** Note that we always put 1-31 on the list, regardless of what the last date of * the specified month is. This is an advanced programming technique called * "laziness," not to be confused with "fuzzy logic." * * Try selecting February 31! Because we use the Date class, it gets fixed up * to look like a legal day in March. */ private void addDateChoice( Date inDate ) { add( new Label( "Date:", Label.RIGHT ) ); Choice theChoice = new Choice(); for( int i = 1; i <= 31; i++ ) { theChoice.addItem( Integer.toString( i ) ); } theChoice.select( Integer.toString( inDate.getDate() ) ); add( theChoice ); mDateChoice = theChoice; } /** Standard behavior, we just make a big list from 00:00 to 23:30 in increments of * 30. There is also padding so we always end up with 4-digit numbers. * * We don't check the 'hhmm' time specified in inDate, so it is possible to * get some confusing behavior here when we try to select a non-existent value. * This should probably be error checked, but then this applet would lose its * rustic "just developed" feel. * * There is also a bug in some older Java VMs with Choice fields with a lot of values. * You may see the Choice field drawn with some sort of weird appearance for some * values toward the end of the time Choice field. * * The choice contains a colon in the middle to help visually distinguish if from * the year (e.g., year 2000 and time 20:00). */ private void addTimeChoice( Date inDate ) { add( new Label( "Time:", Label.RIGHT ) ); Choice theChoice = new Choice(); String theHour; for( int i = 0; i <= 23; i++ ) { if( i < 10 ) { theHour = "0" + Integer.toString( i ); } else { theHour = Integer.toString( i ); } theChoice.addItem( theHour + ":00" ); theChoice.addItem( theHour + ":30" ); } // choose the specified time int theTime = inDate.getHours() * 100 + inDate.getMinutes(); if( theTime == 0 ) { theHour = "0000"; } else if( theTime < 100 ) { theHour = "00" + Integer.toString( theTime ); } else if( theTime < 1000 ) { theHour = "0" + Integer.toString( theTime ); } else { theHour = Integer.toString( theTime ); } theChoice.select( makeTime( theHour ) ); add( theChoice ); mTimeChoice = theChoice; } /* Easy one. Nothing strange here, except that the legal values are 30-300 in * even 30s. */ private void addDurationChoice( int inDuration ) { add( new Label( "Duration:", Label.RIGHT ) ); Choice theChoice = new Choice(); for( int i = 1; i <= 10; i++ ) { theChoice.addItem( Integer.toString( i * 30 ) ); } theChoice.select( Integer.toString( inDuration ) ); add( theChoice ); mDurationChoice = theChoice; } /* Really easy. Legal values from 1-64. */ private void addChannelChoice( int inChannel ) { add( new Label( "Channel:", Label.RIGHT ) ); Choice theChoice = new Choice(); for( int i = 1; i <= 64; i++ ) { theChoice.addItem( Integer.toString( i ) ); } theChoice.select( Integer.toString( inChannel ) ); add( theChoice ); mChannelChoice = theChoice; } /** The addXxxxCaption methods are similar to the addXxxxChoice methods, except * that both elemts are Label objects. The first one is the lable, the second * is where we write some piece of output. * * In this case, we add the place to put the generated PlusCode. */ private void addPlusCodeCaption() { add( new Label( "PlusCode:", Label.RIGHT ) ); // label for field Font boldFont = new Font( "Dialog", Font.BOLD, 10 ); // make a BOLD font for PlusCode mCodeField.setFont( boldFont ); // set the mCodeField font to BOLD add( mCodeField ); // add it to the frame // mCodeField is where we put the result of the Encode class } /** */ private void addDayCaption( Date inDate ) { add( new Label( "Day:", Label.RIGHT ) ); mDayField.setText( inDate.toString() ); add( mDayField ); // where we write the string representation of the Date } /** */ private int getIntParam( String inName, int inDefaultValue ) throws NumberFormatException { String theValue = this.getParameter( inName ); if( theValue == null ) { return inDefaultValue; } return Integer.parseInt( theValue ); } /** Well, this is easy! Since we use only Choices and Labels (which all know how * to Paint themselves), we don't even need a Paint method. */ // public void // paint( // Graphics g ) // { // } } /** * class: Encoder * Usage: Encoder theEncoder = new Encoder( ... ); // inits and encodes * theEncoder.getPlusCode(); // gets PlusCode * * The Encoder class really stands alone. You can call if from wherever you * need a PlusCode. It is really simple (you can't even change settings after * the instance is constructed), and the only public methods are the two * constructors (all int parameters, and Date plus channel and duration), and * getPlusCode(), which retreives the PlusCode. * * Since Java is garbage collected, why not just construct one, fetch the * code and let it go out of scope and get thrown away for you? Cool! * * The C code from which Encoder was adapted did not document the algorithm * used for PlusCode generation, and in fact the C code's authors claimed to * not underdtand how it works. In this grand tradition, I won't document the * algorithm either, and I'll admit that I have not studied the code all that * carefully, so I don't know how it works either. */ /* VCR+ Encoder. Generates VCR+ codes. Copyright (C) 1996-2000 Doyle B. Myers This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ import java.util.Date; public class Encoder { /** * Encoder (inYear, inMonth, inDate, inTime, inDuration, inChannel) * inYear 96 * inMonth 11 * inDay 24 * inStart 1530 * inDuration 30 * inChannel 9 (these values give PlusCode 4453) * * Trivia: the example 11/24/96 15:30 program on channel 9 (in Seattle) is the * PBS show "Victory Garden". The resultant PlusCode should be 4453. * Hi, Roger, Roger, and Marion! */ Encoder ( int inYear, int inMonth, int inDate, int inTime, int inDuration, int inChannel) throws Exception { initEncoder(inYear, inMonth, inDate, inTime, inDuration, inChannel); encode(); // this generates the PlusCode from the specified parameters } /** * Encoder (inDate, inDuration, inChannel) * inDate Date(96, 11, 24, 16, 30) * inDuration 30 * inChannel 9 * * Trivia: the example 11/24/96 16:30 program on channel 9 (in Seattle) is the * PBS show "The New Yankee Workshop". The resultant PlusCode should be * 9144. Norm rules! */ Encoder ( Date inDate, int inDuration, int inChannel) throws Exception { initEncoder( inDate.getYear(), inDate.getMonth(), inDate.getDate(), inDate.getHours() * 100 + inDate.getMinutes(), inDuration, inChannel); encode(); } /** * initEncoder * The constructors use initEncoder() for all the common initialization. */ public void initEncoder ( int inYear, int inMonth, int inDate, int inTime, int inDuration, int inChannel) throws Exception { mYear = inYear; mMonth = inMonth; mDate = inDate; mTime = inTime; mDuration = inDuration; mChannel = inChannel; // check for valid args if (mMonth < 1 || mMonth > 12) { System.err.println("Invalid month (1 - 12)"); throw new Exception(); } if (mDate < 1 || mDate > 31) { System.err.println("Invalid day of month (1 - 31)"); throw new Exception(); } // can only have up to 64 channels, due to algorithm used! (pab) if (mChannel < 1 || mChannel > 64) { System.err.println("Invalid channel (1 - 64)"); throw new Exception(); } int trailers = mTime % 100; if (trailers != 0 && trailers != 30) { System.err.println("Invalid start time (00 or 30)"); throw new Exception(); } if (mTime < 0 || mTime > 2359) { System.err.println("Invalid start time (0000 - 2359)"); throw new Exception(); } trailers = mDuration % 30; if (trailers != 0 || mDuration < 30 || mDuration > 300) { System.err.println ("Illegal duration (even half hours, 30 - 300 minutes)\n"); throw new Exception(); } } /** * getPlusCode * Simple accessor, just returns the generated PlusCode. */ public int getPlusCode() { return theCode; } /** * upchuck */ private void upchuck() throws IllegalArgumentException { System.err.println ("Usage: encode month day year channel starting_time length_in_minutes\n"); System.err.println ("Specify time in military format (0000 = midnight, 1430 = 2:30 P.M.)\n"); System.err.println ("Only understands half-hour time increments; generates 6 digit vcrplus codes or smaller.\n"); throw new IllegalArgumentException(); } /** * encode * This is where the PlusCode is generated. If you want to understand the * algorithm, read the code. It seems to be fashionable to remain ignorant * about how this works. * * from the original C code */ public void encode() { int doneflag; int trailers; int tblidx; int s1_out; int bot3; int top5; int quo; int rem; int s4_out = 0; int s5_out = 0; int cval; int year; year = mYear % 100; /* get the t bits and the c bits */ cval = mChannel - 1; tblidx = lookup (mTime, mDuration); if (tblidx < 0) { return; } /* from them infer what must have been step 4 & step 5 results */ interleave (tblidx, cval); s4_out = t8c5; s5_out = t2c1; /* find the smallest unmapped_top giving correct mapped_top */ top5 = 0; doneflag = 0; /* if the mapped_top is zero then top and offset are zero */ if (s4_out == 0) { top5 = 0; ofout = 0; doneflag = 1; } while (doneflag == 0) { top5++; offset(mDate, year, top5); if (topout == s4_out) { doneflag = 1; } } /* have two of the three inputs to step 5; determine the rem */ for (rem=0; rem<32; rem++) { topout = (rem + (mDate*(mMonth+1)) + ofout) % 32; if (topout == s5_out) { break; } } quo = mDate - 1; /* assemble the output of step 1 */ bot3 = 1 + rem + 32 * quo; s1_out = bot3 + 1000 * top5; /* invert the mixing */ theCode = func1(s1_out); } /** * offset * from the original C code * * Outputs: * ofout * topout */ private void offset ( int day, int year, int top ) { int i; int t; int tx; int off; int ndigits; int pwr; /* pwr = power of 10 with same # digits as top, ndigits = that many digits */ pwr = 1; ndigits = 0; while (top >= pwr) { ndigits++; pwr *= 10; } pwr /= 10; t = tx = top; off = 0; while (tx > 0) { off += (tx % 10); tx /= 10; } do { for (i=0; i<=year%16; i++) { off += map_top(day, i, t, ndigits) % 10; } t = map_top (day, year, t, ndigits); } while (t < pwr); ofout = (off % 32); topout = (t); } /** * func1 * from the original C code */ private int func1 ( int val) { int ndigits; int pwr; /* find pwr = power of 10 with same # of digits as val, ndigits = that # of digits */ ndigits = 0; pwr = 1; while (val >= pwr) { ndigits++; pwr *= 10; } if (ndigits > 8) { System.err.println( "ERROR: " + val + " has more than 8 digits (it has " + ndigits + "digits)"); upchuck(); } pwr /= 10; do { val = mixup(val, KEY001) % (pwr * 10); } while (val < pwr); return (val); } /** * mixup * Function that performs initial scrambling * from the original C code */ private int mixup ( int x, int y) { int i; int j; int digit; int sum; int[] a = new int[9]; int[] b = new int[9]; int[] out = new int[17]; /* get the digits of x into a[] */ for (i=0; i<9; i++) { digit = x % 10; a[i] = digit; x = (x - digit) / 10; } /* get the digits of y into b[] */ for (i=0; i<9; i++) { digit = y % 10; b[i] = digit; y = (y - digit) / 10; } /* clear out array */ for (i=0; i<17; i++) { out[i] = 0; } for (i=0; i<=8; i++) { for (j=0; j<=8; j++) { out[i+j] += b[j] * a[i]; } } j = 1; sum = 0; for (i=0; i<=8; i++) { sum += j * (out[i] % 10); j *= 10; } return (sum); } /** * map_top * from the original C code */ private int map_top ( int day, int year, int top, int digits) { int d2, d1, d0, y; int n2 = 0; int n1 = 0; int n0 = 0; int f3, f2, f1, f0; y = year % 16; d2 = top / 100; d1 = (top % 100) / 10; d0 = top % 10; f0 = 1; f1 = (y + 1) % 10; f2 = ( ((y+1)*(y+2)) / 2 ) % 10; f3 = ( ((y+1)*(y+2)*(y+3)) / 6 ) % 10; if (digits == 1) { n0 = (d0*f0 + day*f1) % 10; n1 = 0; n2 = 0; } if (digits == 2) { n0 = (d0*f0 + d1*f1 + day*f2) % 10; n1 = (d1*f0 + day*f1) % 10; n2 = 0; } if (digits == 3) { n0 = (d0*f0 + d1*f1 + d2*f2 + day*f3) % 10; n1 = (d1*f0 + d2*f1 + day*f2) % 10; n2 = (d2*f0 + day*f1) % 10; } return (100*n2 + 10*n1 + n0); } /** * lookup * The 480-entry tables of starting time and pgm duration * Returns index number of entry if found, or -1 if not found * from the original C code */ private int lookup ( int inStartTime, int inDuration) { int j; boolean found; // Search for the entry we need found = false; for (j=0; j<480; j++) { if (inStartTime == start[j] && inDuration == leng[j]) { found = true; break; } } if (!found) { System.err.println ( "ERROR: I don't have a table entry for starttime=" + inStartTime + " and duration=" + inDuration + "\n"); j = -1; } return (j); } /** * interleave * output: t2c1, t8c5 * from the original C code */ private void interleave ( int inTableIndex, int inCVal ) { int small; int big; int b; small = 0; big = 0; /* build t2c1 */ b = 0x00000001 & (inTableIndex >> 2); small += (b << 4); b = 0x00000001 & (inTableIndex >> 1); small += (b << 2); b = 0x00000001 & inTableIndex; small += b; b = 0x00000001 & (inCVal >> 1); small += (b << 3); b = 0x00000001 & inCVal; small += (b << 1); /* build t8c5 */ b = 0x00000001 & (inTableIndex >> 8); big += (b << 9); b = 0x00000001 & (inTableIndex >> 7); big += (b << 7); b = 0x00000001 & (inTableIndex >> 6); big += (b << 5); b = 0x00000001 & (inTableIndex >> 5); big += (b << 4); b = 0x00000001 & (inTableIndex >> 4); big += (b << 3); b = 0x00000001 & (inTableIndex >> 3); big += b; b = 0x00000001 & (inCVal >> 5); big += (b << 8); b = 0x00000001 & (inCVal >> 4); big += (b << 6); b = 0x00000001 & (inCVal >> 3); big += (b << 2); b = 0x00000001 & (inCVal >> 2); big += (b << 1); t8c5 = big; t2c1 = small; } // Encoder privates // Project: There are too many (practical) globals, these could // be encapsulated a little better than this. private int mYear; private int mMonth; private int mDate; private int mTime; private int mDuration; private int mChannel; private int t8c5; private int t2c1; private int ofout; private int topout; private int theCode; // the VCRPlus code (result of encode()) static final int KEY001 = 9371; // a magic number private static int[] start = new int[480]; private static int[] leng = new int[480]; static { // static initializer for start[] and leng[] int s; int j; // Start and Length hash tables start[0] = 1830; leng[0] = 30; start[1] = 1600; leng[1] = 30; start[2] = 1930; leng[2] = 30; start[3] = 1630; leng[3] = 30; start[4] = 1530; leng[4] = 30; start[5] = 1730; leng[5] = 30; start[6] = 1800; leng[6] = 30; start[7] = 1430; leng[7] = 30; start[8] = 1900; leng[8] = 30; start[9] = 1700; leng[9] = 60; start[10] = 1400; leng[10] = 30; start[11] = 2030; leng[11] = 30; start[12] = 1700; leng[12] = 30; start[13] = 1600; leng[13] = 120; start[14] = 2000; leng[14] = 30; start[15] = 1500; leng[15] = 30; start[16] = 2000; leng[16] = 120; start[17] = 2100; leng[17] = 120; start[18] = 2000; leng[18] = 60; start[19] = 1800; leng[19] = 120; start[20] = 1900; leng[20] = 60; start[21] = 2200; leng[21] = 60; start[22] = 2100; leng[22] = 60; start[23] = 1400; leng[23] = 120; start[24] = 1500; leng[24] = 60; start[25] = 2200; leng[25] = 120; start[26] = 1130; leng[26] = 30; start[27] = 1100; leng[27] = 30; start[28] = 2300; leng[28] = 30; start[29] = 1600; leng[29] = 60; start[30] = 2100; leng[30] = 90; start[31] = 2100; leng[31] = 30; start[32] = 1230; leng[32] = 30; start[33] = 1330; leng[33] = 30; start[34] = 930; leng[34] = 30; start[35] = 1300; leng[35] = 60; start[36] = 2130; leng[36] = 30; start[37] = 1200; leng[37] = 60; start[38] = 1000; leng[38] = 120; start[39] = 1800; leng[39] = 60; start[40] = 2200; leng[40] = 30; start[41] = 1200; leng[41] = 30; start[42] = 800; leng[42] = 30; start[43] = 830; leng[43] = 30; start[44] = 1700; leng[44] = 120; start[45] = 900; leng[45] = 30; start[46] = 2230; leng[46] = 30; start[47] = 1030; leng[47] = 30; start[48] = 1900; leng[48] = 120; start[49] = 730; leng[49] = 30; start[50] = 2300; leng[50] = 60; start[51] = 1000; leng[51] = 60; start[52] = 700; leng[52] = 30; start[53] = 1300; leng[53] = 30; start[54] = 700; leng[54] = 120; start[55] = 1100; leng[55] = 60; start[56] = 1400; leng[56] = 60; start[57] = 1000; leng[57] = 30; start[58] = 800; leng[58] = 120; start[59] = 2330; leng[59] = 30; start[60] = 1300; leng[60] = 120; start[61] = 1200; leng[61] = 120; start[62] = 900; leng[62] = 120; start[63] = 630; leng[63] = 30; start[64] = 1800; leng[64] = 90; start[65] = 600; leng[65] = 30; start[66] = 530; leng[66] = 30; start[67] = 0; leng[67] = 30; start[68] = 2330; leng[68] = 120; start[69] = 2200; leng[69] = 90; start[70] = 1300; leng[70] = 90; start[71] = 900; leng[71] = 60; start[72] = 1630; leng[72] = 90; start[73] = 1600; leng[73] = 90; start[74] = 1430; leng[74] = 90; start[75] = 2000; leng[75] = 90; start[76] = 1830; leng[76] = 90; start[77] = 600; leng[77] = 60; start[78] = 1200; leng[78] = 90; start[79] = 30; leng[79] = 30; start[80] = 130; leng[80] = 120; start[81] = 0; leng[81] = 60; start[82] = 1700; leng[82] = 90; start[83] = 0; leng[83] = 120; start[84] = 800; leng[84] = 60; start[85] = 700; leng[85] = 60; start[86] = 2130; leng[86] = 120; start[87] = 500; leng[87] = 30; start[88] = 1530; leng[88] = 90; start[89] = 1130; leng[89] = 120; start[90] = 1100; leng[90] = 120; start[91] = 830; leng[91] = 90; start[92] = 2230; leng[92] = 90; start[93] = 900; leng[93] = 90; start[94] = 2130; leng[94] = 90; start[95] = 1630; leng[95] = 120; start[96] = 2330; leng[96] = 60; start[97] = 100; leng[97] = 120; start[98] = 1400; leng[98] = 90; start[99] = 130; leng[99] = 30; start[100] = 330; leng[100] = 120; start[101] = 1500; leng[101] = 90; start[102] = 1500; leng[102] = 120; start[103] = 2300; leng[103] = 120; start[104] = 1900; leng[104] = 90; start[105] = 800; leng[105] = 90; start[106] = 430; leng[106] = 30; start[107] = 300; leng[107] = 30; start[108] = 1330; leng[108] = 120; start[109] = 1000; leng[109] = 90; start[110] = 700; leng[110] = 90; start[111] = 100; leng[111] = 30; start[112] = 2330; leng[112] = 90; start[113] = 330; leng[113] = 30; start[114] = 200; leng[114] = 30; start[115] = 2230; leng[115] = 120; start[116] = 400; leng[116] = 30; start[117] = 600; leng[117] = 120; start[118] = 400; leng[118] = 120; start[119] = 230; leng[119] = 30; start[120] = 630; leng[120] = 90; start[121] = 30; leng[121] = 60; start[122] = 2230; leng[122] = 60; start[123] = 100; leng[123] = 60; start[124] = 30; leng[124] = 120; start[125] = 2300; leng[125] = 90; start[126] = 1630; leng[126] = 60; start[127] = 830; leng[127] = 60; start[128] = 0; leng[128] = 90; start[129] = 1930; leng[129] = 120; start[130] = 930; leng[130] = 120; start[131] = 2030; leng[131] = 90; start[132] = 500; leng[132] = 60; start[133] = 1730; leng[133] = 60; start[134] = 200; leng[134] = 120; start[135] = 1930; leng[135] = 90; start[136] = 930; leng[136] = 90; start[137] = 1730; leng[137] = 120; start[138] = 630; leng[138] = 120; start[139] = 1830; leng[139] = 60; start[140] = 1430; leng[140] = 60; start[141] = 1130; leng[141] = 90; start[142] = 30; leng[142] = 90; start[143] = 830; leng[143] = 120; start[144] = 1030; leng[144] = 90; start[145] = 1430; leng[145] = 120; start[146] = 100; leng[146] = 90; start[147] = 730; leng[147] = 120; start[148] = 2030; leng[148] = 120; start[149] = 300; leng[149] = 90; start[150] = 300; leng[150] = 120; start[151] = 1330; leng[151] = 90; start[152] = 1230; leng[152] = 90; start[153] = 230; leng[153] = 90; start[154] = 2130; leng[154] = 60; start[155] = 1130; leng[155] = 60; start[156] = 1830; leng[156] = 120; start[157] = 630; leng[157] = 60; start[158] = 530; leng[158] = 60; start[159] = 200; leng[159] = 60; start[160] = 1530; leng[160] = 120; start[161] = 730; leng[161] = 60; start[162] = 600; leng[162] = 90; start[163] = 1730; leng[163] = 90; start[164] = 400; leng[164] = 60; start[165] = 730; leng[165] = 90; start[166] = 430; leng[166] = 90; start[167] = 430; leng[167] = 60; start[168] = 130; leng[168] = 90; start[169] = 1230; leng[169] = 120; start[170] = 130; leng[170] = 60; start[171] = 230; leng[171] = 120; start[172] = 1930; leng[172] = 60; start[173] = 300; leng[173] = 60; start[174] = 1030; leng[174] = 120; start[175] = 200; leng[175] = 90; start[176] = 330; leng[176] = 60; start[177] = 500; leng[177] = 120; start[178] = 930; leng[178] = 60; start[179] = 230; leng[179] = 60; start[180] = 2030; leng[180] = 60; start[181] = 400; leng[181] = 90; start[182] = 1530; leng[182] = 60; start[183] = 430; leng[183] = 120; start[184] = 1330; leng[184] = 60; start[185] = 1230; leng[185] = 60; start[186] = 330; leng[186] = 90; start[187] = 1030; leng[187] = 60; start[188] = 500; leng[188] = 90; start[189] = 530; leng[189] = 120; start[190] = 530; leng[190] = 90; start[191] = 1100; leng[191] = 90; // Elements 192 thru 479 are quite algorithmical s = 2330; for (j=192; j<240; j++) { start[j] = s; leng[j] = 150; if (0==(j%2)) s-=30; else s-=70; } s = 2330; for (j=240; j<288; j++) { start[j] = s; leng[j] = 180; if (0==(j%2)) s-=30; else s-=70; } s = 2330; for (j=288; j<336; j++) { start[j] = s; leng[j] = 210; if (0==(j%2)) s-=30; else s-=70; } s = 2330; for (j=336; j<384; j++) { start[j] = s; leng[j] = 240; if (0==(j%2)) s-=30; else s-=70; } s = 2330; for (j=384; j<432; j++) { start[j] = s; leng[j] = 270; if (0==(j%2)) s-=30; else s-=70; } s = 2330; for (j=432; j<480; j++) { start[j] = s; leng[j] = 300; if (0==(j%2)) s-=30; else s-=70; } } }