jDrum эмулятор ритм студий

Предисловие: у меня оборудована студия, в студию я решил докупить электронные midi ударные, инструмент с падами из линейки: medeli, akai, novation.

Для разработки на компьютере установлен Linux (Ubuntu), программное обеспечение выше упомянутых девайсов в Linux не поддерживается, а заморочки с wine и виртуальной машиной или переключение между операционными системами того не стоят.

Решил разработать простой инструмент для написания ритмов.

Скачать и протестировать программу можно по этой ссылке.

Проектирование

Проектирование начал с рисования интерфейса в NetBeans:

image

Принцип работы

Активное текстовое поле для загрузки сэмпла на линию.

16 кнопок при нажатии на которые происходит воспроизведение сэмпла установаленного на линию.

Кнопка Play воспроизводит звуки по колонкам с установаленными на них сэплами с определенной задержкой (если на линии установлен сэмпл и кнопка нажата).

Код наглядно

JDrum.java в этом классе расположены:

  1. Запуск фрейма.
  2. Основная частить логики.
  3. Наборы переменных.

JDrum.java

/*  * To change this license header, choose License Headers in Project Properties.  * To change this template file, choose Tools | Templates  * and open the template in the editor.  */ package jdrum;  import java.io.BufferedReader; import java.io.File; import java.io.InputStreamReader; import java.util.Arrays; import java.util.List;  /**  *  * @author dj DNkey  */ public class JDrum {     /**      * pads values      */     public static int[] pads = {         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0     };          /*     * pads in line 1     */     public  static Integer[] line1Pads = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};     /**      * pads in line 2      */     public  static Integer[] line2Pads = {17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32};     /**      * pads in line 3      */     public  static Integer[] line3Pads = {33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48};     /**      * pads in line 4      */     public  static Integer[] line4Pads = {49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64};     /**      * pads in line 5      */     public  static Integer[] line5Pads = {65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80};     /**      * pads in line 6      */     public  static Integer[] line6Pads = {81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96};           /**      * pads in column 1      */     public  static int[] column1     = {1,17,33,49,65,81};          /**      * pads in column 2      */     public  static int[] column2     = {2,18,34,50,66,82};     /**      * pads in column 3      */     public  static int[] column3     = {3,19,35,51,67,83};     /**      * pads in column 4      */     public  static int[] column4     = {4,20,36,52,68,84};     /**      * pads in column 5      */     public  static int[] column5     = {5,21,37,53,69,85};     /**      * pads in column 6      */     public  static int[] column6     = {6,22,38,54,70,86};     /**      * pads in column 7      */     public  static int[] column7     = {7,23,39,55,71,87};     /**      * pads in column 8      */     public  static int[] column8     = {8,24,40,56,72,88};     /**      * pads in column 9      */     public  static int[] column9     = {9,25,41,57,73,89};     /**      * pads in column 10      */     public  static int[] column10    = {10,26,42,58,74,90};     /**      * pads in column 11      */     public  static int[] column11    = {11,27,43,59,75,91};     /**      * pads in column 12      */     public  static int[] column12    = {12,28,44,60,76,92};         /**      * pads in column 13      */     public  static int[] column13    = {13,29,45,61,77,93};     /**      * pads in column 14      */     public  static int[] column14    = {14,30,46,62,78,94};     /**      * pads in column 15      */     public  static int[] column15    = {15,31,47,63,79,95};     /**      * pads in column 16      */     public  static int[] column16    = {16,32,48,64,80,96};               /**      * Sound files bind on lines 1-10      */          public static Sound line1Sound  = null;          public static Sound line2Sound  = null;          public static Sound line3Sound  = null;      public static Sound line4Sound  = null;      public static Sound line5Sound = null;          public static Sound line6Sound  = null;                /**      * play speed      */     public  static int speed    = 35;               public  static boolean play = false;          public static Main frame;                 /**      *       * @param args the command line arguments      */     public static void main(String[] args) {                  new Player().start();                  frame = new Main();                  frame.setVisible(true);                       }     /**      * Play object Sound in new Thread      * @param sound       */     public static synchronized void play(Sound sound){                  if(sound != null){                                  new PlaySound(sound).start();                     }              }           public static synchronized void  loadSound(File file){        //  sound      }             /**       * Play pressed pad       * @param padNum        */      public static synchronized void  playPad(int padNum){                    //change pads value 1 to 0, 0 to 1                     if(pads[padNum - 1] == 0){              JDrum.pads[padNum - 1] = 1;           } else{              JDrum.pads[padNum - 1] = 0;           }                /**            * Check line            */                                if(pads[padNum - 1] == 1){              playLine(padNum);           }       }            /**       * play sound file on line where press pad       * @param padNum        */      public static synchronized void playLine(int padNum){            int line = getPadLine(padNum);             /**            * Play sound from line            */                                             if(line == 1){                 JDrum.play(line1Sound);             }              if(line == 2){                 JDrum.play(line2Sound);             }              if(line == 3){                 JDrum.play(line3Sound);             }              if(line == 4){                 JDrum.play(line4Sound);             }              if(line == 5){                 JDrum.play(line5Sound);             }              if(line == 6){                 JDrum.play(line6Sound);             }       }            /**       * get line of pressed pad       * @param padNum       * @return        */      public static synchronized int getPadLine(int padNum){                      int line = 0;            List<Integer> list;                                list = Arrays.asList(line1Pads);                      if(list.contains(padNum)){               line = 1;           }                                 list = Arrays.asList(line2Pads);                      if(list.contains(padNum)){               line = 2;           }                                 list = Arrays.asList(line3Pads);                      if(list.contains(padNum)){               line = 3;           }                                 list = Arrays.asList(line4Pads);                      if(list.contains(padNum)){               line = 4;           }                                 list = Arrays.asList(line5Pads);                      if(list.contains(padNum)){               line = 5;           }                                            list = Arrays.asList(line6Pads);                      if(list.contains(padNum)){               line = 6;           }                      return line;      }            /**       * Save JDrum project to file .drum       * @param fileName        */      public static void save(String fileName){                   //load JDrum settings to save class         Save save = new Save();                   save.pads = JDrum.pads;                   if(line1Sound != null){             save.line1Sound  = line1Sound.file.getAbsolutePath();         }              if(line2Sound != null){              save.line2Sound  = line2Sound.file.getAbsolutePath();         }                  if(line3Sound != null){             save.line3Sound  = line3Sound.file.getAbsolutePath();         }                  if(line3Sound != null){             save.line4Sound  = line4Sound.file.getAbsolutePath();         }                  if(line5Sound != null){             save.line5Sound  = line5Sound.file.getAbsolutePath();         }                  if(line6Sound != null){             save.line6Sound  = line6Sound.file.getAbsolutePath();         }           save.save(fileName);                        }            /**       * Open saved file and load to JDrum       * @param filePath        */      public static void open(String filePath){          Save save = new Save();         save = save.load(filePath);                  Sound sound;                           //line1Sound = new File(save.line1Sound);                  if(save.line1Sound != null){                      sound      = new Sound();              sound.loadFile(new File(save.line1Sound));              line1Sound = sound;                          Main.jTextField1.setText(line1Sound.file.getName());                  }                           if(save.line2Sound != null){                          sound      = new Sound();                                  sound.loadFile(new File(save.line2Sound));                  line2Sound = sound;                          Main.jTextField2.setText(line2Sound.file.getName());                      }                            if(save.line3Sound != null){                          sound      = new Sound();                                  sound.loadFile(new File(save.line3Sound));                  line3Sound = sound;                          Main.jTextField3.setText(line3Sound.file.getName());          }                                    if(save.line4Sound != null){                      sound      = new Sound();              sound.loadFile(new File(save.line4Sound));              line4Sound = sound;                          Main.jTextField4.setText(line4Sound.file.getName());          }                                             if(save.line5Sound != null){             sound      = new Sound();                                  sound.loadFile(new File(save.line5Sound));                  line5Sound = sound;                          Main.jTextField5.setText(line5Sound.file.getName());          }                   if(save.line6Sound != null){             sound      = new Sound();                                  sound.loadFile(new File(save.line6Sound));                  line6Sound = sound;                          Main.jTextField6.setText(line6Sound.file.getName());          }                  JDrum.pads = save.pads;                  frame.changeButton(JDrum.pads);                }      public static void startRecording() {                  String command = "audio-recorder -c start";                  String output  = executeCommand(command);      }          public static void stopRecording() {          String command = "audio-recorder -c stop";                  String output = executeCommand(command);      }          public static String executeCommand(String command) {  		StringBuffer output = new StringBuffer();  		Process p; 		try { 			p = Runtime.getRuntime().exec(command); 			p.waitFor(); 			BufferedReader reader =                              new BufferedReader(new InputStreamReader(p.getInputStream()));                          String line = "";			 			while ((line = reader.readLine())!= null) { 				output.append(line + "\n"); 			}  		} catch (Exception e) { 			e.printStackTrace(); 		}  		return output.toString();  	} }

Player.java демон:

  1. Запуск звуков по колонкам, если на линии расположен сэмпл и нажата кнопка.
  2. Player запускает классы PlaySound которые отрабатывают в отдельном потоке.

Player.java

/*  * To change this license header, choose License Headers in Project Properties.  * To change this template file, choose Tools | Templates  * and open the template in the editor.  */ package jdrum;  import java.lang.reflect.Field; import java.util.logging.Level; import java.util.logging.Logger; import static jdrum.JDrum.playLine;  /**  *  * @author nn  */ public class Player extends Thread {          Field field;     String columnName;          int[] column;          public int step = 1;          public int stopFlag = 0;          public Player() {                  setDaemon(true);      }                  public void run() {          while (true) {              if(JDrum.play){                                  try {                                          //get column from JDrum by step 1-10                     columnName = "column" + step;                                          field = JDrum.class.getDeclaredField(columnName);                                          field.setAccessible(true);                                                               column = (int[]) field.get(null);                                                               //play pads from column                     for(int i = 0;i <= 5;i++ ){                          //System.out.println(columnName);                                                                           if(JDrum.pads[column[i] - 1] == 1){                                                          JDrum.playLine(column[i]);                         }                     }                                                               //next step                     step++;                     if(step == 17){                         step = 1;                         stopFlag++;                                                  if(stopFlag == 2){                             JDrum.play = false;                             stopFlag = 0;                         }                     }                 } catch (IllegalArgumentException ex) {                     Logger.getLogger(Player.class.getName()).log(Level.SEVERE, null, ex);                 } catch (IllegalAccessException ex) {                     Logger.getLogger(Player.class.getName()).log(Level.SEVERE, null, ex);                 } catch (NoSuchFieldException ex) {                     Logger.getLogger(Player.class.getName()).log(Level.SEVERE, null, ex);                 } catch (SecurityException ex) {                     Logger.getLogger(Player.class.getName()).log(Level.SEVERE, null, ex);                 }             }                                       //speed sleep             try {                 sleep(JDrum.speed * 10);             } catch (InterruptedException e) {                 // handle exception here             }         }     }      }

PlaySound.java запуск звука (класса Sound) в отдельном потоке

PlaySound.java

 /*  * To change this license header, choose License Headers in Project Properties.  * To change this template file, choose Tools | Templates  * and open the template in the editor.  */ package jdrum;  /**  *  * @author nn  */ public class PlaySound extends Thread{           public Sound sound;             public PlaySound(Sound sound){          this.sound = sound;      }            public void run() {         if(sound != null){             sound.play();         }      }  } 

Sound.java класс воспроизведения звука

Sound.java

/*  * To change this license header, choose License Headers in Project Properties.  * To change this template file, choose Tools | Templates  * and open the template in the editor.  */ package jdrum;  import java.io.File; import java.io.IOException; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.Clip; import javax.sound.sampled.DataLine; import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.SourceDataLine;  /**  *  * @author nn  */ public class Sound  {          public boolean playCompleted;           public File file;     public AudioInputStream stream;     public AudioFormat format;     public DataLine.Info info;     public Clip clip;               private final int BUFFER_SIZE = 128000;     private File soundFile;     private AudioInputStream audioStream;     private AudioFormat audioFormat;     private SourceDataLine sourceLine;          public void loadFile(File file){         this.file = file;      }          public void play(){         if(file != null){                          soundFile = file;                       try {                 audioStream = AudioSystem.getAudioInputStream(soundFile);             } catch (Exception e){                 e.printStackTrace();                 System.exit(1);             }              audioFormat = audioStream.getFormat();              DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);                          if (!AudioSystem.isLineSupported(info)) {                 System.out.println("Line not supported"+ info);             }                          try {                 sourceLine = (SourceDataLine) AudioSystem.getLine(info);                 //                 sourceLine.open(audioFormat);             } catch (LineUnavailableException e) {                 e.printStackTrace();                 System.exit(1);             } catch (Exception e) {                 e.printStackTrace();                 System.exit(1);             }              sourceLine.start();                                        int nBytesRead = 0;             byte[] abData = new byte[BUFFER_SIZE];             while (nBytesRead != -1) {                 try {                     nBytesRead = audioStream.read(abData, 0, abData.length);                 } catch (IOException e) {                     e.printStackTrace();                 }                 if (nBytesRead >= 0) {                     @SuppressWarnings("unused")                     int nBytesWritten = sourceLine.write(abData, 0, nBytesRead);                 }             }                                                    /**             try {                 Clip clip = new Clip();                  int waitTime = (int)Math.ceil(clip.getMicrosecondLength()/1000.0);                 Thread.sleep(waitTime);             } catch (InterruptedException ex) {                 Logger.getLogger(Sound.class.getName()).log(Level.SEVERE, null, ex);             } catch (LineUnavailableException ex) {                 Logger.getLogger(Sound.class.getName()).log(Level.SEVERE, null, ex);             }             **/                          sourceLine.drain();             sourceLine.close();         }          }           } 

Выкладывать Main.java не буду там генерация интерфейсов средствами NetBeans, только отдельные интересные моменты:

Main.java

     public Main() {                  initComponents();                //bind load sample        jTextField1.addMouseListener(new SampleEvent(1,this));        jTextField2.addMouseListener(new SampleEvent(2,this));        jTextField3.addMouseListener(new SampleEvent(3,this));        jTextField4.addMouseListener(new SampleEvent(4,this));        jTextField5.addMouseListener(new SampleEvent(5,this));        jTextField6.addMouseListener(new SampleEvent(6,this));                               //bind pad click        Field field;        JButton dynamicButton;                try {                    for (int buttonNum = 1; buttonNum <= 96; buttonNum++) {              field = this.getClass().getDeclaredField("jButton" + buttonNum);              field.setAccessible(true);              dynamicButton = (JButton) field.get(this);                            dynamicButton.setMargin(new Insets(0, 0, 0, 0));                            dynamicButton.addMouseListener(new PadEvent(buttonNum,this));          }                  } catch (NoSuchFieldException ex) {                Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);        } catch (SecurityException ex) {                Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);        } catch (IllegalArgumentException ex) {             Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);         } catch (IllegalAccessException ex) {             Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);         }            }

После инициализации компонентов, нужно назначить события на кнопки:

  1. События вынесены в отдельные классы.
  2. Для назначения событий для 96 кнопок применен Reflection API, который назначает события в цикле по названию (name + i).
SampleEvent.java

/*  * To change this license header, choose License Headers in Project Properties.  * To change this template file, choose Tools | Templates  * and open the template in the editor.  */ package jdrum;  import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.io.File; import java.lang.reflect.Field; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.JFileChooser; import javax.swing.JTextField; import javax.swing.filechooser.FileNameExtensionFilter;  /**  *  * @author nn  */ public class SampleEvent implements MouseListener{          public int fieldNum;     Main frame;          public SampleEvent(int fieldNum, Main frame){          this.fieldNum = fieldNum;          this.frame    = frame;     }          public void mouseClicked(MouseEvent evt) {           if(evt.getButton() == MouseEvent.BUTTON1) {             JFileChooser fileopen = new JFileChooser();                          fileopen.setCurrentDirectory(new java.io.File(System.getProperty("user.dir")));              FileNameExtensionFilter filter = new FileNameExtensionFilter("wav", "wav");             fileopen.setFileFilter(filter);                                                    int ret = fileopen.showDialog(null, "Открыть файл");                             if (ret == JFileChooser.APPROVE_OPTION) {                                                   try {                     File file = fileopen.getSelectedFile();                                                               //setup file name to sample field                     Field field  = frame.getClass().getDeclaredField("jTextField" + fieldNum);                     field.setAccessible(true);                     JTextField value = (JTextField) field.get(this);                                          value.setText(file.getName());                                                               Sound sound = new Sound();                                          sound.loadFile(file);                                          //play                      JDrum.play(sound);                                                            //setup path                      Field f = JDrum.class.getField("line"+ fieldNum +"Sound");                     f.setAccessible(true);                      f.set(null, sound);                                                                                    //System.out.print(JDrum.line1SoundFile);                     //set full path                                          //System.out.println(file.getAbsolutePath());                 } catch (SecurityException | IllegalArgumentException ex) {                     Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);                 } catch (NoSuchFieldException ex) {                     Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);                 } catch (IllegalAccessException ex) {                     Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);                 }                                  }           }                      if(evt.getButton() == MouseEvent.BUTTON3) {                             try {                  Field field  = frame.getClass().getDeclaredField("jTextField" + fieldNum);                  field.setAccessible(true);                  JTextField value = (JTextField) field.get(this);                                    value.setText(" ");                                                      Field f = JDrum.class.getField("line"+ fieldNum +"SoundFile");                  f.setAccessible(true);                  f.set(null, null);                                } catch (NoSuchFieldException ex) {                  Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);              } catch (SecurityException ex) {                  Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);              } catch (IllegalArgumentException ex) {                  Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);              } catch (IllegalAccessException ex) {                  Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);              }                                     }     }      @Override     public void mousePressed(MouseEvent e) {         //throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.     }      @Override     public void mouseReleased(MouseEvent e) {         //throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.     }      @Override     public void mouseEntered(MouseEvent e) {         //throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.     }      @Override     public void mouseExited(MouseEvent e) {        // throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.     }  }

PadEvent.java

/*  * To change this license header, choose License Headers in Project Properties.  * To change this template file, choose Tools | Templates  * and open the template in the editor.  */ package jdrum;  import java.awt.Color; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.lang.reflect.Field; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.JButton;  /**  *  * @author nn  */ public class PadEvent implements MouseListener{          public int pudNum;     Main frame;      public PadEvent(int pudNum,Main frame){         this.pudNum = pudNum;         this.frame  = frame;     }           @Override     public void mouseClicked(MouseEvent evt) {                 if(evt.getButton() == MouseEvent.BUTTON1) {                         Field field;             JButton dynamicButton;              try {                   // change pad color                   field = frame.getClass().getDeclaredField("jButton" + pudNum);                   field.setAccessible(true);                   dynamicButton = (JButton) field.get(this);                    //change color and play pad                   if(!dynamicButton.getBackground().equals(new Color(145,145,145))){                        dynamicButton.setBackground(new Color(145,145,145));                                                                   }else{                       dynamicButton.setBackground(null);                   }                   //play pad                   JDrum.playPad(pudNum);               } catch (SecurityException ex) {                     Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);             } catch (IllegalArgumentException ex) {                  Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);              } catch (IllegalAccessException ex) {                  Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);              } catch (NoSuchFieldException ex) {                  Logger.getLogger(PadEvent.class.getName()).log(Level.SEVERE, null, ex);              }                //cменить значение пада с 1 на 0 или с 0 на 1              //запустить звук назначенный на линнии              //Сменить цвет кнопки сс зеленой на серру и с серой на зеленую              //System.out.println("press" + pudNum);                //cменить значение пада с 1 на 0 или с 0 на 1              //запустить звук назначенный на линнии              //Сменить цвет кнопки сс зеленой на серру и с серой на зеленую              //System.out.println("press" + pudNum);         }      }      @Override     public void mousePressed(MouseEvent e) {         //throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.     }      @Override     public void mouseReleased(MouseEvent e) {         //throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.     }      @Override     public void mouseEntered(MouseEvent e) {        // throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.     }      @Override     public void mouseExited(MouseEvent e) {         //throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.     }      } 

Конечно как у любой альфа версии программы возникают ошибки:

javax.sound.sampled.LineUnavailableException: line with format PCM_SIGNED 44100.0 Hz, 16 bit, stereo, 4 bytes/frame, little-endian not supported. at com.sun.media.sound.DirectAudioDevice$DirectDL.implOpen(DirectAudioDevice.java:513) at com.sun.media.sound.AbstractDataLine.open(AbstractDataLine.java:121) at com.sun.media.sound.AbstractDataLine.open(AbstractDataLine.java:153) at jdrum.Sound.play(Sound.java:68) at jdrum.PlaySoundThread.run(PlaySoundThread.java:24) /home/nn/.cache/netbeans/8.2/executor-snippets/run.xml:53: Java returned: 1 BUILD FAILED (total time: 1 minute 57 seconds)

Ошибка возникает насколько я понял после многоклатного назначения и нажатия клавиш из за занятой линии.

Думаю дальнейшие развитие программы будет в сторону:

  1. Изменение воспроизведения wav файлов на midi.
  2. Добавления нот.
  3. Регулятор звука на дорожке.
FavoriteLoadingДобавить в избранное
Posted in Без рубрики

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *