jDrum emulador de ritmo de estudio

Prefacio: Tengo un estudio equipado, en el estudio decidí comprar baterías midi electrónicas, un instrumento con pads de la línea: medeli, akai, novation.

Linux (Ubuntu) está instalado en la computadora para el desarrollo, el software de los dispositivos anteriores no es compatible con Linux, y los problemas con wine y una máquina virtual o el cambio entre sistemas operativos no valen la pena.

Decidí desarrollar un instrumento simple para escribir ritmos.



Descargue y pruebe el programa en este enlace .

Diseño


El diseño comenzó dibujando una interfaz en NetBeans:

imagen

Principio de funcionamiento


Campo de texto activo para cargar una muestra en una línea.

16 botones cuando se presiona, que reproduce la muestra instalada en la línea.

El botón Reproducir reproduce sonidos en los altavoces con las muestras instaladas en ellos con cierto retraso (si se instala una muestra en la línea y se presiona el botón).

Codifica claramente


JDrum.java en esta clase se encuentran:

  1. Inicio de marco.
  2. La parte principal de la lógica.
  3. Conjuntos de variables.

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(); } } 


Demonio Player.java:

  1. Los sonidos de inicio en columnas si se encuentra una muestra en la línea y se presiona el botón.
  2. Player ejecuta clases de PlaySound que funcionan en un hilo separado.

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 start sound (clase Sound) en una secuencia separada

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(); } } } 


Clase de reproducción de sonido 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(); } } } 


No subiré Main.java a la generación de interfaces mediante NetBeans, solo algunos puntos interesantes:

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); } } 


Después de inicializar los componentes, debe asignar eventos a los botones:

  1. Los eventos se colocan en clases separadas.
  2. Para asignar eventos a 96 botones, se utiliza la API Reflection, que asigna eventos en un bucle por nombre (nombre + 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. } } 


Por supuesto, como cualquier versión alfa del programa, se producen errores:
javax.sound.sampled.LineUnavailableException: línea con formato PCM_SIGNED 44100.0 Hz, 16 bit, estéreo, 4 bytes / frame, little-endian no compatible. en com.sun.media.sound.DirectAudioDevice $ DirectDL.implOpen (DirectAudioDevice.java ∗ 13) en com.sun.media.sound.AbstractDataLine.open (AbstractDataLine.java:121) en com.sun.media.sound.AbstractDataLine .open (AbstractDataLine.java:153) en jdrum.Sound.play (Sound.java:68) en jdrum.PlaySoundThread.run (PlaySoundThread.java:24) /home/nn/.cache/netbeans/8.2/executor-snippets /run.xml:53: Java devuelto: 1 ERROR EN LA CONSTRUCCIÓN (tiempo total: 1 minuto 57 segundos)
El error surge, según tengo entendido, después de múltiples citas y pulsaciones de teclas debido a una línea ocupada.

Creo que el mayor desarrollo del programa será hacia:

  1. Cambia la reproducción de archivos wav a midi.
  2. Añadiendo notas.
  3. Control de volumen en la pista.


ACTUALIZACIÓN
1. Reemplazó el algoritmo de reproducción.
2. Se borran piezas de código innecesarias.
3. Sincronización mejorada.
4. Controlador de velocidad agregado.
5. Se agregó una velocidad de reproducción de ahorro en el proyecto.
6. Detener mueve la reproducción al principio.
7. Puede ver la ubicación de reproducción del bucle.

Source: https://habr.com/ru/post/es428744/


All Articles