3

I am programming a little drum sequencer, a roland tr808 knockoff with 16 steps/measure and 16 instruments(=drum samples). User has a gui where he can thus create a 16x16 pattern.

However, if a sample is played more than once in quick succession, it often just gets played once. Say, I got a bassdrum on step 1, 5, 9 and 13 and tempo's 130BPM, it sometimes plays just the bd on 1 and 9, and sometimes the ones on 5 and/or 13 as well. If the sample is very short or the tempo is slow, the chances are higher that every step in the pattern is played correctly. So I assume that the audio line doesn't like it when I try to play a sample again when it hasn't finished yet.

But actually I thought I'd taken that into account in my code. I'd be really thankful if someone told me what's wrong with my code.

Here's my complete code as suggested by Andrew Thompson, modified so that it takes some samples from the internet. Loading them takes a bit, though. the part causing the issue is probably the play() method in the Instrument class:

package testbox;

import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.sound.sampled.*;


public class boomboxtest {
    public static void main(String[] args) {

        Sequencer seq = new Sequencer();
        //bassdrum
        seq.toggleInstrument(0,0);
        seq.toggleInstrument(0,4);
        seq.toggleInstrument(0,8);
        seq.toggleInstrument(0,12);

        //snare
        seq.toggleInstrument(1,4);
        seq.toggleInstrument(1,12);

        //Hihat
        seq.toggleInstrument(2, 2);
        seq.toggleInstrument(2, 6);
        seq.toggleInstrument(2, 10);

        //Bongo
        seq.toggleInstrument(3, 6);
        seq.toggleInstrument(3, 10);

        seq.setTempo(130);
        seq.play();

    }
}


class Sequencer {
    private Mixer mixer;
    private List<SequencerListener> listeners = new ArrayList<SequencerListener>();
    public static final int INSTR_COUNT = 4;
    private int tempo_bpm = 120;
    private ExecutorService executor;
    private int current_step = 0;
    private int current_max_step = 16;
    private boolean[][] pattern = new boolean[32][INSTR_COUNT];
    private ArrayList<Instrument> instruments;
    Line[] lines = new Line[16];
    private SequencerEngine seq;

    private String[] filenames = {"http://www.canadianmusicartists.com/sample/kick_02.wav", "http://www.canadianmusicartists.com/sample/snare01.wav", "http://www.canadianmusicartists.com/sample/H_closedhat_01.wav", "http://www.canadianmusicartists.com/sample/bongo01.wav"};
    public Sequencer() {
        seq = new SequencerEngine();
        try{ 
            Mixer.Info[] mixerInfo = AudioSystem.getMixerInfo();
            mixer = AudioSystem.getMixer(mixerInfo[0]);
        } catch (Exception e) {e.printStackTrace();}


        instruments = new ArrayList<Instrument>(INSTR_COUNT);
        for (int i = 0; i < INSTR_COUNT; i++) {
            System.out.println("Loading instrument " + i);
            Instrument instr = new Instrument(filenames[i], mixer);
            instruments.add(instr);
            lines[i] = instr.getLine();
        }
        syncMixer();

        executor = Executors.newCachedThreadPool();
        executor.submit(seq);
    }




    public void syncMixer() {
        if (mixer.isSynchronizationSupported(lines, false)) {
            mixer.synchronize(lines, false);
        } else {
            System.out.println("No hay synchronisado");
        }
    }

    public boolean isPlaying() {
        return seq.getRunning();
    }

    public boolean toggleInstrument (int instrument, int beat) {
        pattern[beat][instrument] = !pattern[beat][instrument];
        return pattern[beat][instrument];
    }

    public void play() {
        seq.toggleRun(true);
    }

    public void pause() {
        seq.toggleRun(false);
    }

    public void stop() {
        pause();
        setCurrent_step(0);
    }

    public int getTempo() {
        return tempo_bpm;
    }

    public void setTempo(int tempo) {
        if (tempo < 30) {
            tempo = 30;
        } else if (tempo > 200) {
            tempo = 200;
        } else {
            this.tempo_bpm = tempo;
        }
    }

    public int getCurrent_step() {
        return current_step;
    }

    public void setCurrent_step(int current_step) {
        this.current_step = current_step;
    }

    public boolean[][] getPattern() {
        return pattern;
    }

    public void kill() {
        seq.kill();
        executor.shutdownNow();
    }

    public void addListener(SequencerListener toAdd) {
        listeners.add(toAdd);
    }

    public class SequencerEngine implements Runnable{
        private boolean running;
        private boolean alive = true;

        public void run() {
            while( getAlive()) {
                while (getRunning()) {
                    if (current_step >= current_max_step) {
                        current_step = 0;
                    }



                    for (; current_step < current_max_step ; current_step++) {
                        stepListen();                       
                        if(!getRunning()) {
                            break;
                        }
                        long time = System.currentTimeMillis();
                        long steptime = 60000/(4*tempo_bpm);


                        for (int k = 0; k < INSTR_COUNT; k++) {
                            if (pattern[current_step][k]) {
                                instruments.get(k).play();  
                            }
                        }

                        while((System.currentTimeMillis()-time) < steptime) {}
                    }
                }

            }
        }


        public void stepListen() {
            for (SequencerListener sl : listeners) {
                sl.stepEvent(current_step);
            }
        }

        public boolean getRunning() {
            return running;
        }

        public boolean getAlive() {
            return alive;
        }

        public void toggleRun(boolean toggle) {
            running = toggle;
        }

        public void kill() {
            alive = false;
        }
    }
}

class Instrument {
    private String name;
    private File soundFile;
    private AudioInputStream stream;
    private AudioFormat format;
    private DataLine.Info info;
    private Clip clip;
    private Mixer mixer;

    public Instrument(String filename, Mixer mixer ) {
        this.name = filename;
        try {
            //soundFile = new File("sounds/" + filename);

            URL url = new URL(filename);

            this.mixer = mixer;

            //stream = AudioSystem.getAudioInputStream(soundFile);
            stream = AudioSystem.getAudioInputStream(url);
            format = stream.getFormat();
            info = new DataLine.Info(Clip.class, format);
            clip = (Clip) mixer.getLine(info);
            clip.open(stream);

        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }



    public void play() {
        clip.stop();
        clip.setFramePosition(0);
        clip.start();
    }

    public Line getLine() {
        return clip;
    }
}

interface SequencerListener {
    void stepEvent(int current_step);
}

The samples are of rather questionable quality, but especially the bassdrum sample illustrates my problem really good.

  • For better help sooner, post a [MCVE] or [Short, Self Contained, Correct Example](http://www.sscce.org/). – Andrew Thompson Apr 27 '16 at 02:46
  • *"I am programming a little drum sequencer,.."* Consider using MIDI for this instead of sampled sounds. – Andrew Thompson Apr 27 '16 at 02:47
  • I followed your suggestion about Minimal, Complete and Verifiable, Andrew. However, the whole point of this program is that I will be able to play my own custom samples. I don't see how javax.sound.midi would support that. If you DO know a slick solution for using samples however, please share. I'd happily switch, because the builtin midi sequencer is really nice and easy. – UnbescholtenerBuerger Apr 27 '16 at 04:12
  • No. It is three classes in different packages, & references the missing `EightOhEightGUI` class. An MCVE should be a ***single*** copy/paste/compile/run to see the problem, that collection of codes does not meet that level of simplicity for others to test. Note that for the 'single copy/paste' does not mean an MCVE cannot contain more than one class, it just means they cannot be declared `public` (or put in separate packages!). – Andrew Thompson Apr 27 '16 at 04:19
  • okay, got it. Thanks for the hint. You'd still need to put your own samples in though. – UnbescholtenerBuerger Apr 27 '16 at 04:37
  • *"You'd still need to put your own samples in though."* You might hot-link to a short sample (load it within the code, directly from the internet) or [generate a tone](http://stackoverflow.com/a/7782749/418556) at run-time. The `generateTone()` method is the one of interest in that linked example. – Andrew Thompson Apr 27 '16 at 04:49
  • Alright, now its completely standalone thanks to preloading some other samples from the internet. takes a few seconds. Thanks for the advice! – UnbescholtenerBuerger Apr 27 '16 at 07:01

0 Answers0