Friday, 31 August 2012

And on and on

Quick update

  • create capability to change point size in use on the notes
  • create separate capability to change point size in use on text objects
  • Fix printing logic so it scales appropriately and doesn't do crazy things with landscape mode 
  • Rests - large omission, as most scores have a rest in them!
  • Cut, Copy & Paste
  • Undo
  • Fixing file open so it doesn't pop a new window
  • Fixing the exit without saving logic
  • Toolbar buttons to make entering widgets easier

And uncovered and fixed more bugs, mostly in reading the score back in from xml, and now finally understand (I think), the printing stuff.

Tuesday, 21 August 2012

So I forgot something

OK the to-do list should have been
  • Cut, Copy & Paste
  • Undo
  • Drag and Drop between windows (low priority for me)
  • Irregular groupings - most of the code is there, just need to finish it
  • Rests - large omission, as most scores have a rest in them!
  • Do something more meaningful with the Properties variable (score, author, version, defaults etc)
  • Toolbar buttons to make entering widgets easier
  • Fixing file open so it doesn't pop a new window
  • Fixing the exit without saving logic
  • Dragging selections around, keyboard is very slow as it's pixel by pixel
  • Fix printing logic so it scales appropriately and doesn't do crazy things with landscape mode
  • create capability to change point size in use on the notes
  • create separate capability to change point size in use on text objects
  • create all the capability to have images as part of the score - only kidding, that's V2.
  • Finish adding Tie logic to XML save and open
Note we're done on a couple now; for now. There's still improvements could be made to the tie quadratic curve rendering, it's not using arcs that gradually separate at the crown then converge again at the tail, filled obviously. It's just using a common brush stroke. Later, a version "artwork" maybe.

So now I'm going to force rank the critical subset before my trusted testers get their hands on it.
  1. create capability to change point size in use on the notes
  2. create separate capability to change point size in use on text objects
  3. Fix printing logic so it scales appropriately and doesn't do crazy things with landscape modeCut, 
  4. Rests - large omission, as most scores have a rest in them!
  5. Cut, Copy & Paste
  6. Undo
  7. Fixing file open so it doesn't pop a new window
  8. Fixing the exit without saving logic
  9. Toolbar buttons to make entering widgets easier
I need to think of a 10th, so then it's a 10 step plan, doesn't sound right as a 9 step :)

CCP is interesting - looking forward to that challenge but want to get the core drawing stuff finished, so any changes to actions etc have the full range of objects to work on. Then making them undoable actions is going to be fun!

Note that last one, my initial tester said they don't care about that, they'll learn the keystrokes - guru!!


Monday, 20 August 2012

XML, Cathedrals and all that

The idea about storing ties, which in the internal model are effectively any to any joins between any notes in a manuscript object, is working. It's all about the order in which the XML is written out, i.e. in ascending note order in the manuscript so we can maintain a tag level, like a running count of open ties. Then when it's read back in, new tie objects are created when we hit an increment of the tag, and stored, filed and popped off the stack when we decrement it.

Works well.

Then I saw a tweet about the software and IT industry in general and it was written from the point of view that a book about cathedrals and bazaars (now if I were a kind blogger and cared enough about this book I'd go get a proper reference to it here, but on the odd chance anyone reads my musings, well folks, just go search for it using your favourite internet trawler), and how sick the industry is because we didn't learn anything when we want through the dot-com boom, how unix has been poisoned yah-de-yah.

Well the thoughts it inspired for me were, well, I don't know everything to finish building this "cathedral" known currently as the Drum Score Editor, I certainly knew a lot less when I started about loads of things which are relevant to designing and implementing this stuff. I'm learning on this journey, using lots of good training and experience gained over the years, and adapting it, allowing myself to continue to refine my understanding as I solve a new challenge, sometimes even using different techniques to solve similar problems, just to see which works best, which is more elegant and so on. This is especially true in the current XML push - a language structure legible to neither human nor computer. Many ways of accomplishing the same thing.

So I'm an enthusiastic amateur, I know loads about rudimental snare drumming, music theory, java programming, object oriented design, yah-de-yah but I DO NOT KNOW IT ALL!! I don't think anyone does. What I'm doing is bringing all that together, learning as I go, and producing something that is unique in the way it addresses the needs of the authoring drummer. I'm not going to blurt it all out here but there is something special in mind beyond getting the basic manuscript rendering capability usable and useful.

Will it be used by more than 1 drummer (ahem, me), maybe, and if it does great. Will it change the way the world consumes IT, thinks about operating system design and the whole nine yards? No. It won't.

Cathedrals and Bazaars, hopefully there's still space in this industry for some individual to produce something of value to a niche audience, that appreciate it's delivery and can help make it self-sustaining long after I stop being useful! In a way it's a Cathedral, as I'm the sole arbiter of the internal designs, funding and doing all the development and make it work in an integral way. It's not a Bazaar with lots of contributors throwing stuff in. But I'm not comfortable calling it a cathedral given my strictly amateur status - maybe the Ideal Palace of the Postman Cheval, La Maison Ideal du Facteur Cheval at Hauterives in France. I visited that when we lived there, was fascinated then. And now I think my software is likely the same thing ...

Friday, 17 August 2012

One more push

Been coding away like mad. To be fair this post is more for me to list what needs doing before someone can start to play with the app. The current work package is ensuring that if someone saves a file with a score they've made, then when it is read in they've not lost anything. Before this we've been using Java serialisation - ok for testing but if you want to persist the score and continue coding then serialisation is no use as the objects when read in will bear no resemblance to the current ones. Saving as XML and more importantly how the XML is read back in, allows us to increment the software.

This is pretty much complete with one exception - ties (or binds or grouping depending on your preference). Haven't figured out a design for storing them as XML at the moment. Got an idea, just need to work it through.

Then we are still missing:

  • Cut, Copy & Paste
  • Undo
  • Drag and Drop between windows (low priority for me)
  • Irregular groupings - most of the code is there, just need to finish it
  • Rests - large omission, as most scores have a rest in them!
  • Do something more meaningful with the Properties variable (score, author, version, defaults etc)
  • Toolbar buttons to make entering widgets easier
  • Fixing file open so it doesn't pop a new window
  • Fixing the exit without saving logic
  • Dragging selections around, keyboard is very slow as it's pixel by pixel
Onwards!


Thursday, 9 August 2012

Contextual Component Actions

If our score has multiple objects on the page, e.g. 3 text areas, 1 manuscript area, and you have an action called for example "setBackGroundColour" in each object.

If you have a menuitem and toolbar component that fire this action, how does it know which of the objects to fire it into? The answer has to be the currently focused one.

How does it know what the currently focused one is? That's a problem because by selecting a menu item or the toolbar component, you actually change focus.

There's a solution using delegate actions presented on the web, but this doesn't seem to exactly fit the bill. It's not the first way of solving this problem that sprang to mind, but the solution has a basis in this  article http://www.javalobby.org/java/forums/t19448.html

My thoughts were, what if we put a FocusListener on a target object, which, when focus is gained it tells the action, it's the current target. It should probably tell the Action that it's no longer the target when it loses focus. This is a co-operative approach, all objects must implement the protocol defined or it breaks down. So what happens though, as surely you would lose focus when you click the menubar? The DefaultEditorKit must solve this somehow with it's cut, copy, paste actions.

So let's work this out, we extend the default AbstractAction to include some new capabilities. A setTarget method which can be called to make it null, ie called from within a lost focus method, or called with an Action specifying the Action whose actionPerformed method should be invoked, ie called when focus gained.

This means the focus lost and gained methods for an object need to know the list of actions to set as target actions. Also means they must know the list of master actions to control.

Suddenly starting to seem complex with lots of list passing and storing going on. Further reading shows that this is considered in http://www.javalobby.org/java/forums/t19448.html. It works fundamentally by tracking the focus owner, checking every time focus changes if the new focus owner has an action of the monitored name.

So for us we have a set of application level actions (things file open, window, help); then some document level actions (zoom, new staff, new text) and then each page level component in the document has a set of actions. What the page level component needs to do is tell the document level actions that if it gets fired to pass it their way.

Now the immediate challenge with that is some of these page level components (e.g. the JMusicComponent) have 40 or 50 actions. That's an awful lot to be switching on and off every time focus is changed. That would mean there's a corresponding document level action for each of those.

There's probably another level of hierarchy too, as within the page level component there are lots of other widgets which potentially have actions. At that level in some of the components we are talking widgets, meaning objects that are not based on a swing component, especially the 2D drawing based objects. Not being based on Swing components at that level means no focus transitions to plug into, there will need to be a handcrafted way of achieving the same.

This is when I started thinking about swapping menuitem action references. Each JMenuItem has an associated action, what if we could swap those at run time, would that save writing all those wrapper actions to manage the arbitration and switching? The way the app is structured, all the menu items get created in one go when we create or open a document. All the code for doing this sits at the frame  level, reaching into the document for actions or reaching up to the application level for actions. This makes the frame level part of the document level, i.e. it manages the frame, and puts a panel within it which we call the view.

This provides little scope for dynamically adding or removing menu items. Guess it's easier to have actions greyed out like this, as no matter where in the app you enable or disable an action it will work on the menu item.

So it looks like the way ahead is to reproduce every single components actions that need to have menuitems or buttons associated with them. One last thing to explore is the permanent or temporary loss of focus - it could be that going for menus and buttons results in temporary loss of focus which is something we could hook into, for example, on temporary loss, we leave our actions enabled, on permanent loss we nullify them out. On regain of permanent focus we plug our action set back in. On testing this it works a treat.

Here's the base class used that all document level actions that need to be pointed at the last permanently focused component are derived from:

package score;

import java.awt.event.ActionEvent;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.Icon;

public class SwitchingAction extends AbstractAction {

 private static final long serialVersionUID = 1L;
 private Action targetAction=null;

 public SwitchingAction() {
  super();
 }
 
 public SwitchingAction(String name) {
  super(name);
 }
 
 public SwitchingAction(String name, Icon icon) {
  super(name,icon);
 }
 
 @Override
 public void actionPerformed(ActionEvent arg0) {
  if (targetAction != null) {
   targetAction.actionPerformed(new ActionEvent(arg0.getSource(), 
            arg0.getID(), 
            arg0.getActionCommand(), 
            arg0.getWhen(), 
            arg0.getModifiers()) 
          );
  }

 }

 public Action getTargetAction() {
  return targetAction;
 }

 public void setTargetAction(Action targetAction) {
  this.targetAction = targetAction;

  if (targetAction==null) {
   this.setEnabled(false);
  } else {
   this.setEnabled(targetAction.isEnabled());
  }
 }

}



Then in a component that needs to worry about getting it's actions switched in when it has permanent focus ...

 @SuppressWarnings("serial")
 Action setBoldAction = new AbstractAction() {

  @Override
  public void actionPerformed(ActionEvent arg0) {
   System.out.println("Going bold");
   setTextFace(Font.BOLD);
  }
 };
 
        [snip]

        // in the object constructor ...
 docText.addFocusListener(this);

        [snip]


 @Override
 public void focusGained(FocusEvent arg0) {
  // TODO Auto-generated method stub
  System.out.println(arg0);
  if (!arg0.isTemporary()) {
   // tell the document action we want the action events
   DocActions docActions=this.getDocActions();
   SwitchingAction sba=docActions.getDocSetBoldAction();
   sba.setTargetAction(setBoldAction);
  }
 }

 @Override
 public void focusLost(FocusEvent arg0) {
  // TODO Auto-generated method stub
  System.out.println(arg0);
  if (!arg0.isTemporary()) {
   // tell the document action to forget us
   DocActions docActions=this.getDocActions();
   SwitchingAction sba=docActions.getDocSetBoldAction();
   sba.setTargetAction(null);
  }
 }


We have all the document level actions, including the switching ones defined in a DocActions object for ease of reference.





Tuesday, 7 August 2012

MIDI Levelset: The Learning Journey

Reading up on MIDI there's some terminology I need to get straight. If I have what I've been calling a capture device, i.e. a drum pad that sends signals when it's been hit, in the MIDI world that seems to be known as a MIDI Sequencer, and the software I'm wanting to write is a MIDI Synthesizer. Although strictly speaking I'm not synthesizing the MIDI events to produce audible music, but written music. Wonder if there are purists in the MIDI world than would take umbrage :)

Wait a minute! Is the software actually a sequencer, as the Java API describes something that takes MIDI data from a file as a sequencer that then sends the data to a synthesizer to play it. So does that mean we need a sequencer to receive the MIDI data and do something with it, and then send it to the synthesizer which then renders the notes (visibly, not audibly).

And then we find out there's raw MIDI and time-tagged MIDI. Definition being that the raw MIDI is what you need to send to an external MIDI instrument, and and raw MIDI data coming into the computer has to be time-tagged before hitting the sequencer. This sounds like a definition written by experts, with the single purpose of confusing a layperson!!

If I simplify it to be the data hitting the computer port from the drum pad is raw MIDI, something needs to time tag that input so it can be stored and processed. That stored and processed time-tagged MIDI can then be sent to the rendering engine (synthesizer). A MIDI in port seems to be the definition of what does the time-tagging on the way in.

If we were interested in sending the music to an external MIDI device for playback, we would use a MIDI out port to strip the time tagging but supposedly send the MIDI instructions at the right time.

So having struggled with the high level description the API was giving me, it's the actual architecture description that really firms up the definitions. It becomes clearer when we start talking about streaming MIDI as opposed to sequenced MIDI. Streaming is real-time and therefore not time-tagged. Sequenced is time-tagged and can be stored for later processing or playback.

So this means we are wanting to receive streamed MIDI from the drum pad, tag the notes with a timestamp, meaning we are writing a sequencer at this stage. When these get passed to the brains of the app which translates these into visible notation, that's synthesizing I believe BUT the gurus say it's a sequencer that translates to realtime transmission of MIDI events. Damn it, I'm calling all of this my software sequencer, with no synthesizer needed, just my renderer which takes the time tagged MIDI events from the sequencer and stores them in the data model!

Reading through the Java Sound API, the use of Sequencer and Synthesizer becomes even clearer. The high level architecture for what we want to achieve is:

  • use a Sequencer to record MIDI information representing drum strokes through a MIDI IN port
  • store it in a MIDI file
  • use a Sequencer to play that MIDI file to a Synthesizer
  • the Synthesizer doesn't play the notes but creates JDrumNote objects in a JManuscript

Being me, the first thing I do is challenge the complexity here. I probably do want to capture everything in a MIDI file as an option for debugging, so a Sequencer is necessary to receive the time-tagged MidiEvents and it should be wired into the MIDI IN port using the API in a standard way.

But, instead of playing the events back to a synthesizer, which appears to strip the timing information as it receives them when they should be played, I'd rather pass the events to a software brain that does the translation to JDrumNotes - that brain will need access to the timing information of note just the current instruction but also previous, and potentially any future note. Also, the Synthesizer model needs a whole bunch more learning, which looks mostly to only be relevant if we were going to actually generate sound.

But this can be broken down I hope. If the Sequencer can capture the Sequence in a file, we could have another Sequencer which, later on, reads that file and analyzes the contents to do the translation to JDrumNotes. That's the journey I'll set out on, let's see where we end up.

One other consideration into how we do this is MIDI sends note-on and note-off events. Drumming is a pretty staccato operation, the on and off will be fairly close together. We need to understand what the conventions are in drumming voice in the MIDI world.

(Oh dear, just come across the term Quantization in the context of digital music. After reading it the way I think about it is it's like "snap to grid" functionality in drawing software, i.e. if you're slightly late or early, the software puts the note where it thinks it should be. I didn't know there was a phrase for this but it's something I had thought would be needed in interpreting the incoming drum strokes as part of the notation brain)

Looks like there's lots of articles on how to create your own effects to synthesize drum rolls by editing sequences but phew this is complex trying to assimilate all the terminology in use as well as figure out what we need here.

So there's the concept of a drum brain out there, into which you plug rubber pads which basically generate a voltage when hit. The brain then converts those to MIDI events. I believe what I need is a drum pad trigger MIDI controller or some such arrangement of those words!

Talking to a kit drummer (Jack at Nevada) who uses MIDI in the studio, his advice is do not go for cheaper options like the Alesis PercPad due to the rapidity of rudimental drumming - you'll get crosstalk across the pads and there's a question mark if they'll pick up the press rolls accurately enough and even keep up with the event flow. TD30 and nothing less - go Roland or Ddrum pickups which clip to the rim of a real drum. We're talking serious cost at that level though, but that's the challenge - having the quality needed to meet the challenges of the drumming style.

I think the way ahead is to work out the brain, by using keystrokes to emulate the midievents, maybe even making them look like them, and getting a testrig hooked up to see how we get on with some of the midi setups the guys in the band have.




The Next Level

Some really great drummers saw the software last night for the first time. The feedback was great, really positive, especially the more natural way notes are added and managed compared to the other 2 known pieces of software that create drum manuscripts for pipe bands.

One of the key pieces of feedback was don't stop to pretty it up. Get the functional pieces working at this stage, then go for the next level, i.e. the score capture capability. Apparently others have claimed they were writing software such as this years ago but it's never appeared. I can understand why, this is so much more complex to implement than the high level concept suggests.

It's now time for research again. I've been recommended the Roland TD12 as the right drum pad to capture strokes, it's allegedly good enough to differentiate buzz type strokes as opposed to taps. The other thing that would be useful initially would be ones that can differentiate the weight of the stroke, i.e. how hard it's been hit. We'll need 2 pads, or a pad with 2 separate capture areas, as unless we want to investigate electronics in sticks, I can't think of another way of detecting which hand a stroke has been played with.

Initial trawl shows up http://www.homerecordingnow.com/electronic_drum_pads.php, which has a great index to a number of products. My hopes are high that I'm not looking for something so esoteric that it either doesn't exist or is so expensive it's not viable.

Alesis have a product which looks like a capture only device, which outputs midi signals from 8 different pads. Makes me think if you could learn to hit the top row of pads for buzzes and the bottom row for taps then even if it doesn't understand you could achieve the end goal! Given I don't know enough about MIDI and drum interfaces, that's probably the best place to start. I mean is there even a Java MIDI interface? Yes, time to get immersed in the Java Sound API!

Going deep, back when I've learned something!


Monday, 6 August 2012

DnD and out

Final straw. Java Drag And Drop support is now getting completely removed from the app. I'm struggling with a scenario where I get an array out of bounds exception from Path2D, but only after a DnD operation, if the Path2D hadn't been used before and some other hokey conditions.

Now, having moved to MacOSX to continue I find the various Aqua UI components aren't serializable. Folks it should be very simple - people will want to drag components around, why is this so messed up. All my objects are serializable, some of the mac l&f components aren't and I now suspect bits of the Java2D libraries struggle with this too.

I always suspect myself first, i.e. taking the stance "it's probably my lack of knowledge somewhere" rather than "the tried and tested compliers/run time/operating system is flawed". I'm not spending any more time on trying to figure this out. DnD and it's initial lack of scaling support shocked me, I'm not fighting with it any more, am coding around it.

/rant

Thursday, 2 August 2012

Time Signatures

The regular work days are long and the hour is late but I've got to try and get 30 mins to an hour in each day or this won't move ahead. We're starting on the time signature elements that sit on the staff.

Let's start with an object that represents a time signature. Seeing as the English language doesn't have an agreed lexicon for the top and bottom number you see in a time signature, we face our first decision.

The top number represents the number of beats in a bar, the bottom number tells you the note duration of each beat. Then consider if the top number is greater than 4 then we're in compound time, so you divide the top number by 3 to get the number of beats in the bar. So 2/4 means 2 quarter notes (crotchets) in a bar, and 6/8 means 2 eighth notes (quavers) in a bar.

Then there's C and C with a vertical bar down the middle of it. These are taken today to mean Common and Cut Common time, namely 4/4 and 2/2 respectively. The C is alleged to have come from Circa Perfectum, meaning the perfect circle, i.e. the representation of the whole note. Just stick with Common time for now, I can see why you'd say whole note because 4/4 is 4 times a quarter note. Moving on!

A simple java object to represent this would be

package score.notes.timesigs;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;

import score.notes.JDrumNote;
import score.notes.JStaffElement;

public class TimeSignature extends JStaffElement {

 private static final long serialVersionUID = 1L;
 
 private int top=2;
 private int bottom=2;
 private boolean useCommon=false;
 
 public int getTop() {
  return top;
 }
 
 public void setTop(int top) {
  this.top = top;
  setUseCommon(false);
 }
 
 public int getBottom() {
  return bottom;
 }
 
 public void setBottom(int bottom) {
  this.bottom = bottom;
  setUseCommon(false);
 }

 public boolean isUseCommon() {
  return useCommon;
 }

 private void setUseCommon(boolean useCommon) {
  this.useCommon = useCommon;
 }
 
}

In this class we represent the top and bottom numbers as you'd expect and we deal with the optional use of Common and Cut Common notation using the boolean useCommon as a flag for the mechanism the user has favoured.

Next we need to consider the rendering of a time signature. All the drawing in the drum score app is performed using 2D shapes, with separate zooming and scaling factors applied. The approach taken this time is to use the built-in Java fonts that we can guarantee are available on all platforms. We are warned that in some locales the internationalized characters cannot be guaranteed to render the same everywhere. On the other hand bundling physical fonts with the app to guarantee both their presence and common appearance is a major overhead. To be honest, I'm considering figuring out some Path2D constructs to represent the shapes we need and doing away with fonts entirely - maybe in the future huh!

So to implement this so it works within the zooming and scaling architecture of the score app, we need to derive our object from JStaffElement. This means we need to provide a preDelete hook and a scaleChanged hook.

The preDelete hook is empty in the case of this object, the user can delete it with no consequence to anything around it (this construct is needed for notes which may participate in a tie arrangement with another note, if that's the case we need to delete the tie).

The scaleChanged hook is where we scale the font size in use, to match the scaling of the rest of the document. Note scaling can happen in two ways, firstly that the user wants to zoom in on the screen to fine tune position or just get a better look (zoomFactor), or the user decides they want all the drawing artefacts to be bigger in relation to the page (scaleFactor).

 protected void scaleChanged() {
  double pointSize=getPointSize();
  double zoomFactor=getZoomFactor();
  
  double basePointSize=6.5f; // hardcoding to same as a note's base so it scales proportionately
  double pointScaleFactor=pointSize/basePointSize; 
  
  scaledFontSize = zoomFactor * pointScaleFactor * modelFontSize;
  scaledCutLineWidth = zoomFactor * pointScaleFactor * modelCutLineWidth;
 }
 
The technique used throughout the app is that there's a model size for the artefact and these have been manually baselined across all the artefacts so they start from the same point, and then every change in scale results in the scaledFontSize being recalculated - this is what's used in the paint() routine.

public void paint(Graphics g, JStaffElement prevElement, JDrumNote prevNote, JStaffElement nextElement) {
  super.paint(g,prevElement,prevNote,nextElement);
 
  Graphics2D g2D = (Graphics2D) g;
  Color saveColor = g2D.getColor();
  g2D.setColor(getDrawColor());
  
  // need to get some sizing details from the font. Would prefer not to have to do this in paint() and prepare everything
  // when we set zoom or point size factors.
  
  if (!useCommon) {
   // draw regular numbers
   Font font = new Font(Font.SERIF, Font.PLAIN, (int)scaledFontSize);
   FontMetrics metrics = g2D.getFontMetrics(font);
   g2D.setFont(font);

   // get the height of a line of text in this
   // font and render context
   double fontAscent = metrics.getAscent();
   double fontDescent = metrics.getDescent();

   // figure the width of the larger of top and bottom 
   double topWidth = metrics.stringWidth(Integer.toString(top));
   double bottomWidth = metrics.stringWidth(Integer.toString(bottom));
   setElementWidth(Math.max(topWidth,bottomWidth));

   // gotta figure out where we draw the top and bottom numbers from, x is easy ...
   double tsX = getX();
   double tsTopY = getY() + getAnchorHeight(); // this gives us where the staff line is
   tsTopY -= fontDescent;

   double tsBottomY = getY() + getAnchorHeight() + fontAscent;
   
   g2D.drawString(Integer.toString(top),(float)tsX,(float)tsTopY);
   g2D.drawString(Integer.toString(bottom),(float)tsX,(float)tsBottomY);
  } else {
   // use Common and Cut Common notation
   String common="C";
   Font font = new Font(Font.SANS_SERIF, Font.PLAIN, (int)scaledFontSize);
   FontMetrics metrics = g2D.getFontMetrics(font);
   g2D.setFont(font);

   setElementWidth(metrics.stringWidth(common));
   
   double tsX = getX();
   double tsY = getY() + getAnchorHeight() + (metrics.getAscent()/2) - metrics.getLeading();
   g2D.drawString(common,(float)tsX,(float)tsY);
   
   // need to fill a shape that represents the vertical line if it's cut common
   if (top == 2) {
    
    // the x point is halfway along the width of the C, less half of the width of the line
    double cutX=getX()+(metrics.stringWidth(common)/2.0f);
    
    // the start y point is half of the height of the font above the C
    double cutY=tsY-metrics.getHeight();
 
    // the depth is 40% more than the height of the font
    double cutLength=metrics.getHeight()+(0.4*metrics.getHeight());
    
    cutLine.setFrame(cutX,cutY,scaledCutLineWidth,cutLength);
    g2D.fill(cutLine);
   }
  }
  
  // restore environment
  g2D.setColor(saveColor);
 }

Firstly, note there's a bunch of parameters you won't expect on a regular Swing paint() call, these are not relevant to the TimeSignature class, but are in the base class it's derived from as the JDrumNote staff element needs to have a reference to the notes either side of it, to draw ligatures and ties and so on.

Next we call the super as it takes care of highlighting if this object is in a selected state, then we do the usual cast to Graphics2D, and respect the pen colour that the super has set. The rest is pretty standard font stuff, except note the use of doubles. The whole of the application is written with double precision so a very high quality graphics experience is possible in the artwork. Unfortunately the Font implementations only use integer precision for the point size and the ascent / descent. What we do here by casting to double is ensure we don't lose the x and y double precision for placement but round to nearest whole number for the fonts.

It doesn't make any discernible difference on the screen, it's in the printing that I'm hoping we don't get alignment issues. Would be more straightforward if the whole Java2D library converted to double precision, I hope the font stuff is just legacy that hasn't been addressed yet.

Note the need to deal with the Common and Cut Common time in the paint() method. The font metrics bit confused me at first until I realised Height and Ascent etc are for the max size of character in the font, not the one we're using, i.e. the C!

So for reference, here's the complete working object, with all the elements discussed built in.

package score.notes.timesigs;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;

import score.notes.JDrumNote;
import score.notes.JStaffElement;

public class TimeSignature extends JStaffElement {

 private static final long serialVersionUID = 1L;
 
 final static private double modelFontSize=2.5f;
 final static private double modelCutLineWidth=0.1f;
 
 private int top=2;
 private int bottom=4;
 private boolean useCommon=false;
 
 private double scaledFontSize;
 private double scaledCutLineWidth;
 
 private Rectangle2D cutLine = new Rectangle2D.Double();
 
 public void paint(Graphics g, JStaffElement prevElement, JDrumNote prevNote, JStaffElement nextElement) {
  super.paint(g,prevElement,prevNote,nextElement);
 
  Graphics2D g2D = (Graphics2D) g;
  Color saveColor = g2D.getColor();
  g2D.setColor(getDrawColor());
  
  // need to get some sizing details from the font. Would prefer not to have to do this in paint() and prepare everything
  // when we set zoom or point size factors.
  
  if (!useCommon) {
   // draw regular numbers
   Font font = new Font(Font.SERIF, Font.PLAIN, (int)scaledFontSize);
   FontMetrics metrics = g2D.getFontMetrics(font);
   g2D.setFont(font);

   // get the height of a line of text in this
   // font and render context
   double fontAscent = metrics.getAscent();
   double fontDescent = metrics.getDescent();

   // figure the width of the larger of top and bottom 
   double topWidth = metrics.stringWidth(Integer.toString(top));
   double bottomWidth = metrics.stringWidth(Integer.toString(bottom));
   setElementWidth(Math.max(topWidth,bottomWidth));

   // gotta figure out where we draw the top and bottom numbers from, x is easy ...
   double tsX = getX();
   double tsTopY = getY() + getAnchorHeight(); // this gives us where the staff line is
   tsTopY -= fontDescent;

   double tsBottomY = getY() + getAnchorHeight() + fontAscent;
   
   g2D.drawString(Integer.toString(top),(float)tsX,(float)tsTopY);
   g2D.drawString(Integer.toString(bottom),(float)tsX,(float)tsBottomY);
  } else {
   // use Common and Cut Common notation
   String common="C";
   Font font = new Font(Font.SANS_SERIF, Font.PLAIN, (int)scaledFontSize);
   FontMetrics metrics = g2D.getFontMetrics(font);
   g2D.setFont(font);

   setElementWidth(metrics.stringWidth(common));
   
   double tsX = getX();
   double tsY = getY() + getAnchorHeight() + (metrics.getAscent()/2) - metrics.getLeading();
   g2D.drawString(common,(float)tsX,(float)tsY);
   
   // need to fill a shape that represents the vertical line if it's cut common
   if (top == 2) {
    
    // the x point is halfway along the width of the C, less half of the width of the line
    double cutX=getX()+(metrics.stringWidth(common)/2.0f);
    
    // the start y point is half of the height of the font above the C
    double cutY=tsY-metrics.getHeight();
 
    // the depth is 40% more than the height of the font
    double cutLength=metrics.getHeight()+(0.4*metrics.getHeight());
    
    cutLine.setFrame(cutX,cutY,scaledCutLineWidth,cutLength);
    g2D.fill(cutLine);
   }
  }
  
  // restore environment
  g2D.setColor(saveColor);
 }
 
 public int getTop() {
  return top;
 }
 
 public void setTop(int top) {
  this.top = top;
  setUseCommon(false);
 }
 
 public int getBottom() {
  return bottom;
 }
 
 public void setBottom(int bottom) {
  this.bottom = bottom;
  setUseCommon(false);
 }

 public boolean isUseCommon() {
  return useCommon;
 }

 public void setUseCommon(boolean useCommon) {
  this.useCommon = useCommon;
 }

 @Override
 protected void preDelete() {
  // Nothing to do
 }

 @Override
 protected void scaleChanged() {
  double pointSize=getPointSize();
  double zoomFactor=getZoomFactor();
  
  double basePointSize=6.5f; // hardcoding to same as a note's base so it scales proportionately
  double pointScaleFactor=pointSize/basePointSize; 
  
  scaledFontSize = zoomFactor * pointScaleFactor * modelFontSize;
  scaledCutLineWidth = zoomFactor * pointScaleFactor * modelCutLineWidth;
 }
 
}