émulateur de rythme studio jDrum

Préface: J'ai un studio équipé, dans le studio j'ai décidé d'acheter une batterie midi électronique, un instrument avec des pads de la gamme: medeli, akai, novation.

Linux (Ubuntu) est installé sur l'ordinateur pour le développement, le logiciel des appareils ci-dessus n'est pas pris en charge sur Linux, et les problèmes avec Wine et une machine virtuelle ou la commutation entre les systèmes d'exploitation n'en valent pas la peine.

J'ai décidé de développer un instrument simple pour écrire des rythmes.



Téléchargez et testez le programme sur ce lien .

La conception


La conception a commencé par dessiner une interface dans NetBeans:

image

Principe de fonctionnement


Champ de texte actif pour charger un échantillon sur une ligne.

16 boutons enfoncés, qui reproduisent l'échantillon installé sur la ligne.

Le bouton Play reproduit les sons dans les haut-parleurs avec les échantillons installés dessus avec un certain retard (si un échantillon est installé sur la ligne et que le bouton est enfoncé).

Code clairement


JDrum.java dans cette classe se trouve:

  1. Début de trame.
  2. La partie principale de la logique.
  3. Ensembles 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(); } } 


Démon Player.java:

  1. Démarrage des sons dans les colonnes si un échantillon se trouve sur la ligne et que le bouton est enfoncé.
  2. Le joueur exécute des classes PlaySound qui fonctionnent dans un thread séparé.

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 (class Sound) dans un flux séparé

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


Je ne téléchargerai pas la génération d'interface Main.java à l'aide de NetBeans, seulement quelques points intéressants:

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


Après avoir initialisé les composants, vous devez affecter des événements aux boutons:

  1. Les événements sont placés dans des classes distinctes.
  2. Pour affecter des événements à 96 boutons, l'API Reflection est utilisée, qui attribue les événements dans une boucle par nom (nom + 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. } } 


Bien sûr, comme toute version alpha du programme, des erreurs se produisent:
javax.sound.sampled.LineUnavailableException: ligne au format PCM_SIGNED 44100.0 Hz, 16 bits, stéréo, 4 octets / trame, petit-boutien non pris en charge. sur com.sun.media.sound.DirectAudioDevice $ DirectDL.implOpen (DirectAudioDevice.java ∗ 13) sur com.sun.media.sound.AbstractDataLine.open (AbstractDataLine.java:121) sur com.sun.media.sound.AbstractDataLine .open (AbstractDataLine.java:153) sur jdrum.Sound.play (Sound.java:68) sur jdrum.PlaySoundThread.run (PlaySoundThread.java:24) /home/nn/.cache/netbeans/8.2/executor-snippets /run.xml:53: Java renvoyé: 1 ÉCHEC DE LA CONSTRUCTION (durée totale: 1 minute 57 secondes)
L'erreur se produit, si je comprends bien, après plusieurs rendez-vous et frappes de touches en raison d'une ligne occupée.

Je pense que la poursuite du développement du programme visera à:

  1. Changez la lecture des fichiers wav en midi.
  2. Ajout de notes.
  3. Contrôle du volume sur la piste.


MISE À JOUR:
1. Remplacement de l'algorithme de lecture.
2. Les parties de code inutiles sont supprimées.
3. Amélioration de la synchronisation.
4. Contrôleur de vitesse ajouté.
5. Ajout de la sauvegarde de la vitesse de lecture dans le projet.
6. Arrêter déplace la lecture au début.
7. L'emplacement de lecture en boucle est visible.

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


All Articles