Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8269890

MidiChannel.programChange(bank,program) does not change controller's 0 and 32

    XMLWordPrintable

Details

    Description

      ADDITIONAL SYSTEM INFORMATION :
      OS : Windows 10 Pro x64 build version 1909
      Java Runtime : 14

      A DESCRIPTION OF THE PROBLEM :
      From the docs
      https://docs.oracle.com/en/java/javase/14/docs/api/java.desktop/javax/sound/midi/MidiChannel.html#programChange(int,int)

      It says in order to verify the bank of the channel the formulae is
      int bank = (getController(0) * 128) + getController(32);

      but after calling programChange(bank,program) the instrument changes correctly to the specified bank and program but during verification only getProgram() return the correct program but the bank when obtained using the above formulae always returns 0


      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Steps

      1) Open an MidiSynthesizer

      2) Call Midi.programChange(bank,program)

      3 )Retrive the bank and program as follows
      bank=(channel.getController(0)*128)+channel.getController(32);
      program=channel.getProgram();

      4) Upon printing the values program prints correctly but bank always prints 0

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      The initial bank & program is 0 , 0 which is expected
      after programChange(640,124) the new bank should be 640 and the new program should be 124 that is
      expected output = 640,124
      ACTUAL -
      actual output = 0 ,124 the instrument at bank,program 640,124 plays correctly but retrieving the actual bank always output's zero

      0/0 //Before Bank,Program Change
      Channel=0
      Note=65
      Bank=640
      Program=124
      0/124 //After Bank,Program Change

      ---------- BEGIN SOURCE ----------
      import java.util.Arrays;
      import javax.sound.midi.MidiChannel;
      import javax.sound.midi.MidiSystem;
      import javax.sound.midi.Synthesizer;
      import javax.sound.midi.VoiceStatus;


      public final class Test
      {
       public static void main(String[] args)throws Exception
       {
        try(Synthesizer sys=MidiSystem.getSynthesizer())
        {
         if(!sys.isOpen()){sys.open();}
         
         MidiChannel channel=sys.getChannels()[0];
         
        //Check current bank and program
         System.out.println((channel.getController(0)*128)+(channel.getController(32))+"/"+channel.getProgram());//This calculation to compute bank is part of the documentation
         
         channel.programChange(640,124);//Bank=640,Program=124 Plays Wind Chimes
         channel.noteOn(65,127); //Play Note 65 At Volume 127 [Wind Chime Sounds Correctly]
         
         Thread.sleep(100); //Give Some Time For Note To Turn On
         checkVoice(sys); //Display Voice Status
         
         Thread.sleep(3000); //Play note for a total of 3 seconds
         channel.noteOff(80); //Note Off
         
      //Now recheck bank and program
         System.out.println((channel.getController(0)*128)+(channel.getController(32))+"/"+channel.getProgram());//This Displays Bank 0 with program 124 Which is actually Telephone
        }
       }
       
       private static void checkVoice(Synthesizer syn)
       {
        //The bank changed successfully which is even verified in the active voice
        
        VoiceStatus active=Arrays.stream(syn.getVoiceStatus())
                                 .filter(voice->voice.active)
                                 .findFirst()
                                 .get();
        
        System.out.println("Channel="+active.channel);
        System.out.println("Note="+active.note);
        System.out.println("Bank="+active.bank);
        System.out.println("Program="+active.program);
       }
      }

      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      I tried to find the underlying channel returned by the default synthesizer by causing an intentional null pointer exception with the call synthesizer.loadAlInstruments(null). I then followed the stack trace which lead me to

      ->com.sun.media.sound.SoftSynthesizer

      In it's declared variables i discovered it uses the
      com.sun.media.sound.SoftChannel class which is wrapped in an SoftChannelProxy object and returned to the user.

      In this class i discovered 2 variables of interest

      private int bank
      private int controller[]

      Upon scrolling down and finding the programChange(bank,program) method i discovered an piece of code which could be the source of the potential bug
       
      This code was taken directly from the class

         @Override
          public void programChange(int bank, int program) {
              bank = restrict14Bit(bank);
              program = restrict7Bit(program);
              synchronized (control_mutex) {
                  mainmixer.activity();
                  if(this.bank != bank || this.program != program)
                  {
                      this.bank = bank;
                      this.program = program;
                      current_instrument = null;
                  }
              }
          }

      Notice this method does not change any index value of the controller[] array. It instead just stores the bank and program inside private variables and although the getProgram() method returns the value of this private variable the bank calculation is of no use as the controllers are never changed here

      @Override
          public int getProgram() {
              synchronized (control_mutex) {
                  return program;
              }
          }

      the bank calculation which i stated above returns 0 for now obvious reasons

      This bug again occurs inside the controlChange() method.If you scroll down to the end of the method you will find another bug

      if (controller == 0x00) {
                      bank = /*(bank & 127) +*/ (value << 7);
                      return;
                  }

                  if (controller == 0x20) {
                      bank = (bank & (127 << 7)) + value;
                      return;
                  }

                  this.controller[controller] = value;

      In the if statements for controllers 0x00(0) and 0x20(32) which is associated with bank the new value is again stored in the private variable bank and then the method RETURNS!!. not giving it a chance to be stored in the controller array and therefore this statement

      this.controller[controller] = value;

      never executes for bank change

      Finally the getController() method

      @Override
          public int getController(int controller) {
              synchronized (control_mutex) {
                  // Should only return lower 7 bits,
                  // even when controller is "boosted" higher.
                  return this.controller[controller] & 127;
              }
          }

      will always return 0 for bank.

      These classes and packages are not accessible in my netbeans 12.0 IDE directly which is why i had to follow the stack trace

      The most easiest solution would be to create an public getBank() method but since that's not possible i had to use reflection to extract the bank variable

      private static int getBank(MidiChannel channel)throws Exception
       {
        //Treat this as a special case
        if(Class.forName("com.sun.media.sound.SoftChannelProxy").isInstance(channel))
        {
          Field channelField=channel.getClass().getDeclaredField("channel");
          channelField.setAccessible(true);
          
          Object theActualChannel=channelField.get(channel);
          Field bankField=theActualChannel.getClass().getDeclaredField("bank");
          bankField.setAccessible(true);
          
          return bankField.getInt(theActualChannel); //Return the actual bank
        }
        
        //Hopefully will work in the future
        return (channel.getController(0)*128)+(channel.getController(32));
       }

      This throws a lot of warning messages in my log files for obvious reasons , hopefully this easy fix will be resolved


      FREQUENCY : always


      Attachments

        Activity

          People

            rmahajan Rajat Mahajan
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

            Dates

              Created:
              Updated: