// HalfLife.java - simulated geiger counter with various radioactive // sources. uses audio to produce a click for each random decay if // the click rate is low enough to be handled by the slow java // virtual machine, otherwise a canned audio track with appropriate // click rate is played repeatedly. note: background radiation click // rate is low enough that people can manually count the clicks, so // the actual click count should definitely be displayed (otherwise // they will lose faith in this demo) // Copyright 2003 mark mitchell // No permission is granted for using this applet, or any derivative products, // for commercial purposes // 5/5/2004 written by mark mitchell // 5/20/2004 released to cuesta college physics department // 5/21/2004 removed short gap during wraparound, and stop audio during // shutdown // all time variables are in units of milliseconds // command line parameters are all listed in java console // to compile this and create a jar file for uploading you will need // the java development kit (jdk) from http:java.sun.com/j2se. then // perform the following steps: // 1) javac -target 1.1 HalfLife.java // 2) jar cvf HalfLife.jar *.class // 3) rm *.class // to build sound clip: // 1) record in windows using creative recorder // 2) edit in linux using audacity // 3) convert to java-compatible format (8000 Hz, 8 bit, mono, u-law) // using 'sox -V halflife000.wav -U -r 8000 -b -c 1 halflife000.au' // useful links: // www.hpwt.de/Kern2e.htm has alpha, beta, gamma applet // www.ndt-ed.org/EducationResources/HighSchool/Radiography/radioactivity.htm // education.jlab.org/itselemental // poissonian statistics are generated using page 200 in Numerical Recipes // at http://education.jlab.org/itselemental you can click on an element. // then click on 'View all isotope data' link to get nuclear reactions // and isotopes // // reactions branch frequency transition // --------- ---------------- ---------- // 56 Ba133 + beta -> 55 Cs133 + v 100% electron capture // // 56 Ba133m -> 56 Ba133 + gamma 99.99% isomeric // 56 Ba133m + beta -> 55 Cs133 + v 0.01% electron capture // // 56 Ba137m -> 56 Ba137 + gamma 100% isomeric // // 55 Cs137 -> 56 Ba137m + beta 94.7% beta decay // 55 Cs137 -> 56 Ba137 + beta 5.3% beta decay // // isotope halflife // ------- -------- // 56 Ba133 3848.9 days // 56 Ba133m 38.9 hours // 56 Ba137 stable // 56 Ba137m 2.552 minutes // 55 Cs133 stable // 55 Cs137 30.07 years // // notes // ----- // 1) m after symbol means excited state // 2) electron capture has orbiting electron get captured into nucleus. // this eventually causes a higher electron to fall to ground state, // which emits an x ray // 3) seems to be minor disagreements over branching percentages for // cesium, and half lifes // according to my calculations using the jlab information above: // -Cs137 decay produces betas 100% of the time, and gamma 94.7% // -Ba133 decay produces x rays 100% of the time // -Ba137 decay produces gammas 100% of the time // // screens therefore affect click rates as follows // -paper only stops alpha, which does not affect any isotope here // -foil stops alphas and betas, so it stops half of Cesium-137 (ignore // the factor of two between both betas and gammas are produced, since // the source density, size and purity are also ignored) // -lead stops all except background import java.applet.*; import java.awt.*; import java.awt.event.*; import java.awt.Color.*; import java.lang.*; import java.lang.Math.*; import java.util.*; import java.net.URL; interface ConstClass { // stuff for all panels public static final int appletWidth = 460; public static final Color borderColor = Color.black; public static final Color titleColor = Color.black; // conversion factors; public static final double yearsToMs = 365.25 * 24.0 * 60.0 * 60.0 * 1000.0; public static final double secToMs = 1000.0; // stuff for lab setup panel public static final Font titleFont = new Font("Helvetica", Font.BOLD, 22); public static final Font subtitleFont = new Font("Helvetica", Font.BOLD, 9); public static final String setupTitle = new String("Virtual Radioactivity Lab"); public static final String subtitleDetector = new String("Geiger Detector"); public static final String subtitleScreen = new String("Screen"); public static final String subtitleSource = new String("Source"); public static final int xTitle = 5, yTitle = 24; public static final int xSubtitleDetector = 55, ySubtitleDetector = 36; public static final int xSubtitleScreen = 240, ySubtitleScreen = 36; public static final int xSubtitleSource = 360, ySubtitleSource = 36; public static final int setupHeight = 343; // lab setup panel public static final int xDetector = 0; public static final String imageDetector = new String("img/detector.jpg"); public static final int xScreen = 177; public static final int xSource = 354; public static final double AllXmitted = 1.0; // each click rate has a sound track. click rates must be // monotonically increasing, starting from zero public static final double intervalIn = 5.0 * secToMs; public static final RateSoundTrack rateSoundTracks[] = { new RateSoundTrack(0.0 / intervalIn, false, "sounds/halflife000.au"), new RateSoundTrack(33.0 / intervalIn, true, "sounds/halflife033.au"), new RateSoundTrack(58.0 / intervalIn, true, "sounds/halflife058.au"), new RateSoundTrack(100.0 / intervalIn, true, "sounds/halflife100.au"), new RateSoundTrack(173.0 / intervalIn, true, "sounds/halflife173.au"), new RateSoundTrack(300.0 / intervalIn, true, "sounds/halflife300.au"), new RateSoundTrack(520.0 / intervalIn, true, "sounds/halflife520.au"), new RateSoundTrack(900.0 / intervalIn, true, "sounds/halflife900.au"), new RateSoundTrack(50000.0 / intervalIn, true, "sounds/halflife50k.au") }; // various screens between source and detector public static final Screen screens[] = { new Screen("None", "img/screen_none.jpg", 100.0, 100.0, 100.0, 100.0), new Screen("Paper", "img/screen_paper.jpg", 0.0, 100.0, 100.0, 100.0), new Screen("Foil", "img/screen_foil.jpg", 0.0, 0.0, 100.0, 100.0), new Screen("Lead", "img/screen_lead.jpg", 0.0, 0.0, 0.0, 0.0) }; // various radioactive sources public static final Source sources[] = { new Source("Background", "img/source_background.jpg", 1.0 / secToMs, // convert from reactions/sec 1000.0 * yearsToMs, 0.0, 0.0, 100.0, 0.0), new Source("Cesium-137", "img/source_cesium_137.jpg", 900.0 / secToMs, // convert from reactions/sec 30.07 * yearsToMs, 0.0, 100.0, 94.7, 0.0), new Source("Barium-133", "img/source_barium_133.jpg", 300.0 / secToMs, // convert from reactions/sec 10.58 * yearsToMs, 0.0, 0.0, 100.0, 0.0), new Source("Barium-137", "img/source_barium_137.jpg", 50000.0 / secToMs, // convert from reactions/sec 153.0 * secToMs, 0.0, 0.0, 100.0, 0.0) }; // choices for lab setup panel public static final Rectangle boundsChoiceScreen = new Rectangle(225, setupHeight - 40, 100, 30); public static final Rectangle boundsChoiceSource = new Rectangle(325, setupHeight - 40, 100, 30); // stuff for dials panel public static final int dialsHeight = 50; public static final int xInset = 70; public static final int xBoxWidth = 100; public static final int yBoxHeight = 30; public static final int yFirstRow = 10; public static final int xMinStep = 10; // values over one keep applet on // time by preventing overload public static final Rectangle boundsLblClicks = new Rectangle(10, yFirstRow, 60, yBoxHeight); public static final Rectangle boundsTxtClicks = new Rectangle(xInset, yFirstRow, xBoxWidth, yBoxHeight); public static final int xProgress = appletWidth - xInset - xBoxWidth; public static final Color behindColor = Color.green; public static final Color aheadColor = Color.blue; // sound public static final int wakeupInterval = 15; public static final int dialInterval = 5000; // copyright public static final String copyright = new String ("\1002004 mark mitchell http://home.earthlink.net/~mmc1919"); public static final int xCopyright = 3; int yCopyright = setupHeight - 6; public static final Color copyrightColor = new Color(173, 173, 173); public static final Font legendFont = new Font("Helvetica", Font.PLAIN, 10); } class DialsPanel extends Panel { private Label lblClicks; private TextField txtClicks; private long dialTimeElapsed = 0; // click count private long clicks = 0; // progress of current measurement private double progressCurrent; // progress from 0.0 to 1.0 private int xLastDrawn; // screen coordinate of last drawn progress line private int xCurrent; // coordinate of progress line in progress bar frame // double buffering private Image offScreen; private Graphics gOff; public void advanceTime(long deltaTime) { dialTimeElapsed += deltaTime; setProgress(); } public void clickCountDisplayAndReset() { txtClicks.setText(String.valueOf(clicks)); clicks = 0; dialTimeElapsed = 0; } public void clickCountIncrement() { ++clicks; } public void clickCountSet(long clicks) { this.clicks = clicks; } public DialsPanel() { setLayout(null); setBounds(0, ConstClass.setupHeight, ConstClass.appletWidth, ConstClass.dialsHeight); lblClicks = new Label("Clicks:"); lblClicks.setBounds(ConstClass.boundsLblClicks); add(lblClicks); txtClicks = new TextField(); txtClicks.setBounds(ConstClass.boundsTxtClicks); txtClicks.setEditable(false); add(txtClicks); progressCurrent = 0.0; xLastDrawn = 0; xCurrent = 0; } public void paint(Graphics g) { update(g); } private void setProgress() { int xPrevious = xCurrent; // new position of progress line progressCurrent = dialTimeElapsed / (double) ConstClass.dialInterval; xCurrent = (int) (ConstClass.xBoxWidth * progressCurrent); if (xCurrent < xPrevious) { // just looped around, so repaint entire progress bar repaint(ConstClass.xProgress, ConstClass.yFirstRow, ConstClass.xBoxWidth, ConstClass.yBoxHeight); xLastDrawn = xCurrent; } else { if ((xCurrent - xLastDrawn >= ConstClass.xMinStep) || (xCurrent >= ConstClass.xBoxWidth - 1)) { // repaint the part of progress bar that changed repaint(ConstClass.xProgress + xLastDrawn - 1, ConstClass.yFirstRow, ConstClass.xProgress + xCurrent - xLastDrawn + 2, ConstClass.yBoxHeight); xLastDrawn = xCurrent; } } } public void update(Graphics g) { // double buffering if (offScreen == null) { offScreen = createImage(ConstClass.appletWidth, ConstClass.setupHeight); gOff = offScreen.getGraphics(); } gOff.setColor(ConstClass.aheadColor); gOff.fill3DRect(xCurrent, 0, ConstClass.xBoxWidth - xCurrent, ConstClass.yBoxHeight, true); gOff.setColor(ConstClass.behindColor); gOff.fill3DRect(0, 0, xCurrent, ConstClass.yBoxHeight, true); offScreen.flush(); g.drawImage(offScreen, ConstClass.xProgress, ConstClass.yFirstRow, null); } public boolean wrappedAround() { return (dialTimeElapsed >= ConstClass.dialInterval); } } public class HalfLife extends Applet implements Runnable { // panels, from top to bottom SetupPanel setup; DialsPanel dials; // animation Thread animationThread; // sounds SoundEffects soundEffects; // debug boolean traceSpew; private Random rnd; private long clicksInThisDialInterval() { // return number of clicks for the dial interval, with randomness // according to normal distribution. the setup class will return // click rate adjusted for half life falloff double meanCount = setup.getClickRate() * ConstClass.dialInterval; // this is a poissonian process so the standard deviation // equals the square root of the average count double oneSigma = Math.sqrt(meanCount); long count = (long) (meanCount + oneSigma * rnd.nextGaussian()); if (count < 0) count = 0; return count; } private boolean cmdLineBoolean(String name, boolean dflt) { String valueField = getParameter(name); if (valueField != null) { if (valueField.length() > 0) { boolean value = Boolean.valueOf(valueField).booleanValue(); System.out.println("Name: " + name + " Value: " + value); return value; } } System.out.println("Name: " + name + " Value: " + dflt + " (default)"); return dflt; } private int cmdLineInteger(String name, int dflt) { String valueField = getParameter(name); if (valueField != null) { if (valueField.length() > 0) { int value = Integer.parseInt(valueField); System.out.println("Name: " + name + " Value: " + value); return value; } } System.out.println("Name: " + name + " Value: " + dflt + " (default)"); return dflt; } public void init() { setLayout(null); System.out.println(""); System.out.println("Copyright 2004, Mark Mitchell"); System.out.println("http://home.earthlink.net/~mmc1919"); // java applet will run at this priority int priority = cmdLineInteger("priority", Thread.MIN_PRIORITY); // true to turn on trace spew traceSpew = cmdLineBoolean("traceSpew", false); // true to turn on copyright boolean showCopyright = cmdLineBoolean("showCopyright", false); // initialize dials panel before setup panel since latter calls // former indirectly through applet object dials = new DialsPanel(); add(dials); setup = new SetupPanel(this, traceSpew, showCopyright); add(setup); soundEffects = new SoundEffects(this, traceSpew); rnd = new Random(); // start animation thread animationThread = new Thread(this); animationThread.setPriority(priority); animationThread.start(); } public Image loadSetupImage(String name) { return getImage(getCodeBase(), name); } public void paint(Graphics g) { setup.paint(g); } private long processClick() { soundEffects.playOneShot(); dials.clickCountIncrement(); return startNewInterval(); } private long resetConfiguration() { dials.clickCountDisplayAndReset(); setup.resetSetup(); soundEffects.setClickRate(setup.getClickRate()); setFutureClickCountIfLooping(); return startNewInterval(); } public void run() { int numTimesteps = 0; // units of time are clock ticks, which are one millisecond each long lastTime = System.currentTimeMillis(); long deltaTime = 0; long clickTimeElapsed = 0; double nextInterval = 0.0; resetConfiguration(); while (true) { boolean configJustChanged = setup.configJustChanged(); boolean wrapAround = dials.wrappedAround(); boolean timedOut = (clickTimeElapsed >= (long) nextInterval); if (configJustChanged || wrapAround || timedOut) { if (configJustChanged) { // configuration has just changed so interrupt the current // interval (which may not otherwise complete for weeks or // years!) nextInterval = resetConfiguration(); clickTimeElapsed = 0; } else { if (wrapAround) { // dial interval just timed out wrapProgressBarAround(); } else { // click interval just timed out. this is only // executed for lowest click rates (when not looping // audio) nextInterval = processClick(); clickTimeElapsed = 0; } } } dials.advanceTime(deltaTime); // sleep if (nextInterval > 0) { try { Thread.sleep(ConstClass.wakeupInterval); } catch (InterruptedException e) { } } // update elapsed time long thisTime = System.currentTimeMillis(); deltaTime = thisTime - lastTime; clickTimeElapsed += deltaTime; lastTime = thisTime; } } private void setFutureClickCountIfLooping() { if (soundEffects.getLoop()) { dials.clickCountSet(clicksInThisDialInterval()); } } private long startNewInterval() { // start new interval if not looping long nextInterval; if (soundEffects.getLoop()) { nextInterval = (int) (1000.0 * ConstClass.yearsToMs); } else { nextInterval = (int) (setup.timeToNextClick()); if (traceSpew) { System.out.println("nextInterval " + nextInterval); } } return nextInterval; } public void stop() { // cleanup before exit soundEffects.stop(); } private void wrapProgressBarAround() { // end of dial interval. display click count, and update // mean delay in case half life is short setup.computeMeanDelay(); dials.clickCountDisplayAndReset(); soundEffects.setClickRate(setup.getClickRate()); setFutureClickCountIfLooping(); } } class RateSoundTrack { private double clickRate; // clicks per second private boolean loop; // true to loop, else play once per click private String file; public double getClickRate() { return clickRate; } public String getFile() { return file; } public boolean getLoop() { return loop; } public RateSoundTrack(double clickRate, boolean loop, String file) { this.clickRate = clickRate; this.loop = loop; this.file = file; } } class Screen { private String name; private String file; // transmitted amount between 0 (for none) to 1 (for all) double xmitAlpha, xmitBeta, xmitGamma, xmitXray; public String getFile() { return file; } public String getName() { return name; } public double getXmitAlpha() { return xmitAlpha; } public double getXmitBeta() { return xmitBeta; } public double getXmitGamma() { return xmitGamma; } public double getXmitXray() { return xmitXray; } public Screen(String name, String file, double xmitAlpha, double xmitBeta, double xmitGamma, double xmitXray) { this.name = name; this.file = file; this.xmitAlpha = xmitAlpha / 100.0; this.xmitBeta = xmitBeta / 100.0; this.xmitGamma = xmitGamma / 100.0; this.xmitXray = xmitXray / 100.0; } } class SetupPanel extends Panel implements ItemListener { private HalfLife halflife; private boolean traceSpew; // true to turn on trace spew private boolean showCopyright; // true to show copyright (a bit obnoxious) private Image imgDetector; private Image imgScreen; private Image imgSource; private Choice choiceScreen; private Choice choiceSource; // decay rate private long startTimeCurrentSetup; // milliseconds private double meanDelay; // milliseconds // animation private Random rnd; private boolean restart; // double buffering required since awt (unlike swing) does not have // built-in support for it private Image offScreen; private Graphics gOff; private void changeScreen() { int iScreen = choiceScreen.getSelectedIndex(); if (iScreen >= 0) { loadScreenImage(ConstClass.screens [iScreen].getFile()); } } private void changeSource() { int iSource = choiceSource.getSelectedIndex(); if (iSource >= 0) { loadSourceImage(ConstClass.sources [iSource].getFile()); } } public void computeMeanDelay() { double decayFactor = getDecayFactor(); double clickRate = getClickRate(); // already adjusted by decay factor meanDelay = 1.0 / clickRate; if (traceSpew) { // System.out.println("computeMeanDelay " + // timeWithCurrentSource + ", " + // lambda + ", " + // decay + ", " + // clickRate + ", " + // meanDelay); } } public boolean configJustChanged() { boolean restartSave = restart; restart = false; return restartSave; } private double expdev() { // numerical recipes page 201, and http://www.glenmccl.com/tip_010.htm double dum = 0.0; while (dum == 0.0) { dum = rnd.nextDouble(); } return -Math.log(dum); } public double getClickRate() { // return clicks per millisecond. this would be straightforward // dot product of xmit vector (from screen) and proportion // vector (from source), times reaction rate, except for the // following complications: // 1) decay factor represents exponential decrease over time // 2) background (which is source 0) is ALWAYS present // 3) essentially all of background is transmitteed by any screen // (xmit vector is (1,1,1,1) since screen subtends small solid // angle about detector int iScreen = choiceScreen.getSelectedIndex(); int iSource = choiceSource.getSelectedIndex(); if ((iScreen < 0) || (iSource < 0)) { System.out.println("Screen and/or source is not selected"); System.exit(-1); } Screen scr = ConstClass.screens [iScreen]; Source src; // background contribution src = ConstClass.sources [0]; double clickRate = getDecayFactor() * src.getReactionRate() * (ConstClass.AllXmitted * src.getPropAlpha() + ConstClass.AllXmitted * src.getPropBeta() + ConstClass.AllXmitted * src.getPropGamma() + ConstClass.AllXmitted * src.getPropXray()); // optional nonbackground source of radioactivity if (iSource > 0) { src = ConstClass.sources [iSource]; clickRate += getDecayFactor() * src.getReactionRate() * (scr.getXmitAlpha() * src.getPropAlpha() + scr.getXmitBeta() * src.getPropBeta() + scr.getXmitGamma() * src.getPropGamma() + scr.getXmitXray() * src.getPropXray()); } return clickRate; } public double getDecayFactor() { // decay factor starts out at 1, and drops exponentially long timeWithCurrentSource = System.currentTimeMillis() - startTimeCurrentSetup; double lambda = Math.log(2.0) / getHalflife(); return Math.exp(-1.0 * lambda * timeWithCurrentSource); } private double getHalflife() { int iSource = choiceSource.getSelectedIndex(); if (iSource < 0) { System.out.println("No source is selected"); System.exit(-1); } return ConstClass.sources [iSource].getHalflife(); } public void itemStateChanged(ItemEvent e) { if (traceSpew) { System.out.println("itemStateChanged "); } // SELECTED applies when item was selected by clicking, and // ITEM_STATE_CHANGED applies when item was selected by mouse roller if ((e.getStateChange() == ItemEvent.SELECTED) || (e.getStateChange() == ItemEvent.ITEM_STATE_CHANGED)) { if (e.getItemSelectable() == choiceScreen) { changeScreen(); } else if (e.getItemSelectable() == choiceSource) { changeSource(); } } } private Image loadBackgroundImage(String file) { if (offScreen != null) { // force reloading of offscreen image gOff.dispose(); offScreen = null; } return halflife.loadSetupImage(file); } private void loadDetectorImage(String file) { imgDetector = loadBackgroundImage(file); repaint(0, 0, ConstClass.xScreen, ConstClass.setupHeight); } private void loadScreenImage(String file) { imgScreen = loadBackgroundImage(file); repaint(ConstClass.xScreen, 0, ConstClass.xSource - ConstClass.xScreen, ConstClass.setupHeight); restart = true; } private void loadSourceImage(String file) { imgSource = loadBackgroundImage(file); repaint(ConstClass.xSource, 0, ConstClass.appletWidth - ConstClass.xSource, ConstClass.setupHeight); restart = true; } public void paint(Graphics g) { // moving the code in update() to here and deleting // that function, causes horrible flicker during // initial loading, and complete panel erase followed // by reloading of all three background images. this // approach gives no initial flicker, and causes // redraw of only the appropriate background image update(g); } private void paintCopyright(Graphics g) { g.setFont(ConstClass.legendFont); g.setColor(ConstClass.copyrightColor); g.drawString(ConstClass.copyright, ConstClass.xCopyright, ConstClass.yCopyright); } public void resetSetup() { startTimeCurrentSetup = System.currentTimeMillis(); computeMeanDelay(); } public SetupPanel(HalfLife halflife, boolean traceSpew, boolean showCopyright) { Dimension size = new Dimension(ConstClass.appletWidth, ConstClass.setupHeight); this.halflife = halflife; this.traceSpew = traceSpew; this.showCopyright = showCopyright; setLayout(null); setBounds(0, 0, ConstClass.appletWidth, ConstClass.setupHeight); // backgrounds loadDetectorImage(ConstClass.imageDetector); loadScreenImage(ConstClass.screens [0].getFile()); loadSourceImage(ConstClass.sources [0].getFile()); // available screens choiceScreen = new Choice(); int i; for (i = 0; i < ConstClass.screens.length; i++) { choiceScreen.add(ConstClass.screens [i].getName()); } add(choiceScreen); choiceScreen.setBounds(ConstClass.boundsChoiceScreen); choiceScreen.addItemListener(this); // available sources choiceSource = new Choice(); for (i = 0; i < ConstClass.sources.length; i++) { choiceSource.add(ConstClass.sources [i].getName()); } add(choiceSource); choiceSource.setBounds(ConstClass.boundsChoiceSource); choiceSource.addItemListener(this); rnd = new Random(); restart = false; } public double timeToNextClick() { // compute next delay in milliseconds return meanDelay * expdev(); } public void update(Graphics g) { // double buffering if (offScreen == null) { offScreen = createImage(ConstClass.appletWidth, ConstClass.setupHeight); gOff = offScreen.getGraphics(); } // background images gOff.drawImage(imgDetector, ConstClass.xDetector, 0, this); gOff.drawImage(imgScreen, ConstClass.xScreen, 0, this); gOff.drawImage(imgSource, ConstClass.xSource, 0, this); // title gOff.setColor(ConstClass.titleColor); gOff.setFont(ConstClass.titleFont); gOff.drawString(ConstClass.setupTitle, ConstClass.xTitle, ConstClass.yTitle); gOff.setFont(ConstClass.subtitleFont); gOff.drawString(ConstClass.subtitleDetector, ConstClass.xSubtitleDetector, ConstClass.ySubtitleDetector); gOff.drawString(ConstClass.subtitleScreen, ConstClass.xSubtitleScreen, ConstClass.ySubtitleScreen); gOff.drawString(ConstClass.subtitleSource, ConstClass.xSubtitleSource, ConstClass.ySubtitleSource); if (showCopyright) paintCopyright(gOff); offScreen.flush(); g.drawImage(offScreen, 0, 0, null); } } class SoundEffects { HalfLife halfLife; boolean traceSpew; AudioClip audio; // any click rate is allowed double clickRate; // index of sound effect with largest click rate below clickRate int mappedSoundEffect = -1; public boolean getLoop() { return ConstClass.rateSoundTracks [mappedSoundEffect].getLoop(); } private void loadSoundFile(String file) { // stop any previous loop stopAudio(); // load sound file audio = halfLife.getAudioClip(halfLife.getDocumentBase(), file); } // play audio file once, but only if sound effect is not looping public void playOneShot() { if (!ConstClass.rateSoundTracks [mappedSoundEffect].getLoop()) { stopAudio(); audio.play(); } } public void setClickRate(double clickRate) { // find appropriate click rate for (int i = ConstClass.rateSoundTracks.length - 1;; --i) { RateSoundTrack track = ConstClass.rateSoundTracks [i]; // use this sound effect if it is the last one, or if // its click rate is lower than the specified click rate boolean lower = (clickRate > track.getClickRate()); if ((i == 0) || lower) { // audio will have horrible gap of silence during every // wraparound if file is reloaded, so ignore null transitions if (mappedSoundEffect != i) { if (traceSpew) { System.out.println("setClickRate " + ", new i=" + i + ", old i=" + mappedSoundEffect + ", new r=" + clickRate + ", " + ", track r=" + track.getClickRate() + ", new file=" + track.getFile()); } // this sound clip applies to the specified click rate mappedSoundEffect = i; loadSoundFile(track.getFile()); if (track.getLoop()) { audio.loop(); } } return; } } } public SoundEffects(HalfLife halfLife, boolean traceSpew) { this.halfLife = halfLife; this.traceSpew = traceSpew; } public void stop() { stopAudio(); } private void stopAudio() { try { audio.stop(); } catch (NullPointerException e) { } } } class Source { private String name; private String file; double reactionsPerMs; double halflife; // reaction proportion values between 0 (for none) and 1 (for all). these // need not add up to 1. for example, cesium-137 has beta decay 100% // of the time, and then 94.7% of the time there is an additional // gamma decay, so propBeta is 1 and propGamma is 0.94 double propAlpha, propBeta, propGamma, propXray; public String getFile() { return file; } public double getHalflife() { return halflife; } public String getName() { return name; } public double getPropAlpha() { return propAlpha; } public double getPropBeta() { return propBeta; } public double getPropGamma() { return propGamma; } public double getPropXray() { return propXray; } public double getReactionRate() { return reactionsPerMs; } public Source(String name, String file, double reactionsPerMs, double halflife, double propAlpha, double propBeta, double propGamma, double propXray) { this.name = name; this.file = file; this.reactionsPerMs = reactionsPerMs; this.halflife = halflife; this.propAlpha = propAlpha / 100.0; this.propBeta = propBeta / 100.0; this.propGamma = propGamma / 100.0; this.propXray = propXray / 100.0; } }