Emulador de ritmo do jDrum studio

Prefácio: Eu tenho um estúdio equipado, no estúdio eu decidi comprar bateria eletrônica midi, instrumentos com almofadas da linha: medeli, akai, novation.

O Linux (Ubuntu) está instalado no computador para desenvolvimento, o software dos dispositivos acima não é suportado no Linux e os problemas com o vinho e uma máquina virtual ou a alternância entre sistemas operacionais não valem a pena.

Decidi desenvolver um instrumento simples para escrever ritmos.



Baixe e teste o programa neste link .

Desenho


O design começou desenhando uma interface no NetBeans:

imagem

Princípio de funcionamento


Campo de texto ativo para carregar uma amostra em uma linha.

Quando pressionados, reproduz a amostra instalada na linha.

O botão Play reproduz sons nos alto-falantes com as amostras instaladas neles com um certo atraso (se uma amostra estiver instalada na linha e o botão for pressionado).

Código claramente


O JDrum.java nesta classe está localizado:

  1. Início do quadro.
  2. A parte principal da lógica.
  3. Conjuntos de variáveis.

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


Daemon Player.java:

  1. O início soa nas colunas se uma amostra estiver localizada na linha e o botão for pressionado.
  2. O Player executa classes PlaySound que funcionam em um thread 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 inicia o som (classe Sound) em um fluxo separado

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


Classe de reprodução de som 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(); } } } 


Não enviarei o arquivo Main.java para geração de interface por meio do NetBeans, apenas alguns pontos interessantes:

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


Depois de inicializar os componentes, você precisa atribuir eventos aos botões:

  1. Os eventos são colocados em classes separadas.
  2. Para atribuir eventos a 96 botões, é usada a API do Reflection, que atribui eventos em um loop por nome (nome + 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. } } 


Obviamente, como qualquer versão alfa do programa, ocorrem erros:
javax.sound.sampled.LineUnavailableException: linha com o formato PCM_SIGNED 44100.0 Hz, 16 bits, estéreo, 4 bytes / quadro, little-endian não suportado. em com.sun.media.sound.DirectAudioDevice $ DirectDL.implOpen (DirectAudioDevice.java ∗ 13) em com.sun.media.sound.AbstractDataLine.open (AbstractDataLine.java:121) em com.sun.media.sound.AbstractDataLine .open (AbstractDataLine.java:153) em jdrum.Sound.play (Sound.java:68) em jdrum.PlaySoundThread.run (PlaySoundThread.java:24) /home/nn/.cache/netbeans/8.2/executor-snippets /run.xml:53: Java retornou: 1 BUILD FAILED (tempo total: 1 minuto 57 segundos)
O erro surge, como eu o entendo, após vários compromissos e pressionamentos de teclas devido a uma linha ocupada.

Penso que o maior desenvolvimento do programa será no sentido de:

  1. Mude a reprodução de arquivos wav para midi.
  2. Adicionando notas.
  3. Controle de volume na pista.


ATUALIZAÇÃO:
1. Substituiu o algoritmo de reprodução.
2. Partes de código desnecessárias são limpas.
3. Sincronização aprimorada.
4. Adicionado controlador de velocidade.
5. Adicionado salvando a velocidade de reprodução no projeto.
6. Stop move a reprodução para o início.
7. Você pode ver o local de reprodução do loop.

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


All Articles