Java鼓机音序器:如何基于CheckBox状态实现实时音序控制?
Hey there! Your approach is totally feasible—defaulting to all tracks playing every tick and using checkboxes to mute specific hits makes perfect sense for a drum machine. The problem with your current code is that it generates the entire MIDI sequence upfront; once playback starts, it just loops through that pre-built sequence, so it can't react to real-time checkbox changes.
To fix this, we need to switch from pre-building a sequence to dynamically triggering notes on each tick. Here's how to implement this:
Step 1: Adjust Core Logic
Instead of creating a full sequence ahead of time, we'll use a timer to sync with the tempo, and on each tick, check the current state of your checkboxes to decide which notes to play.
Step 2: Modify the BeatBox Class
Add these instance variables to track the current tick and manage the beat timer:
private Timer beatTimer; private int currentTick = 0; private final int TOTAL_TICKS = 16; // Matches your 16-step grid
Step 3: Update MIDI Setup
Revise the setUpMidi method to initialize the timer instead of building a static sequence:
public void setUpMidi(){ try{ sequencer = MidiSystem.getSequencer(); sequencer.open(); sequencer.setTempoInBPM(120); updateBeatTimer(); // Initialize timer with default tempo }catch(Exception e){ e.printStackTrace(); } }
Step 4: Add Timer & Tick Playback Logic
Add methods to update the timer based on tempo, and play notes for the current tick:
// Calculate timer interval based on current BPM and PPQ (4 ticks per quarter note) private void updateBeatTimer() { if (beatTimer != null) { beatTimer.stop(); } int bpm = (int) sequencer.getTempoInBPM(); int msPerTick = (int) ((60000.0 / bpm) / 4); // 60s * 1000ms / BPM / ticks per beat beatTimer = new Timer(msPerTick, e -> { playCurrentTick(); currentTick = (currentTick + 1) % TOTAL_TICKS; }); } // Dynamically trigger notes based on checkbox state private void playCurrentTick() { try { for (int instrIndex = 0; instrIndex < 16; instrIndex++) { int checkboxIndex = instrIndex * 16 + currentTick; JCheckBox checkBox = checkboxList.get(checkboxIndex); int midiKey = instruments[instrIndex]; // Your rule: Play the note unless the checkbox is selected (muted) if (!checkBox.isSelected()) { // Send note-on event (immediate) ShortMessage noteOn = new ShortMessage(144, 9, midiKey, 100); sequencer.send(noteOn, -1); // Send note-off event (1 tick later to end the note) ShortMessage noteOff = new ShortMessage(128, 9, midiKey, 100); sequencer.send(noteOff, sequencer.getTickPosition() + 1); } } } catch (InvalidMidiDataException ex) { ex.printStackTrace(); } }
Step 5: Update Button & Slider Listeners
Modify the start/stop listeners to control the timer, and update the tempo slider to adjust the timer in real-time:
// Start Button Listener public class MyStartListener implements ActionListener{ public void actionPerformed(ActionEvent a){ currentTick = 0; updateBeatTimer(); beatTimer.start(); sequencer.start(); // Sync sequencer clock } } // Stop Button Listener public class MyStopListener implements ActionListener{ public void actionPerformed(ActionEvent a){ if (beatTimer != null) beatTimer.stop(); sequencer.stop(); sequencer.setTickPosition(0); currentTick = 0; } } // Tempo Slider Listener (updated) slider.addChangeListener(e -> { JSlider source = (JSlider) e.getSource(); int newTempo = source.getValue(); sequencer.setTempoInBPM(newTempo); label.setText("Tempo: " + newTempo); // Update timer if playback is active if (beatTimer != null && beatTimer.isRunning()) { updateBeatTimer(); } });
Step 6: Clean Up Unused Code
You can now remove the buildTrackAndStart, makeTracks, and related sequence-building code since we're no longer using pre-built sequences.
Why This Works
- Real-time responsiveness: Checkbox changes take effect immediately on the next tick, no need to restart playback
- Matches your original idea: Defaults to playing every instrument on every tick; checkboxes mute specific hits
- Simpler maintenance: No need to rebuild and reload the entire sequence when changes are made
内容的提问来源于stack exchange,提问作者NMard

