📄 alsasequencer.java
字号:
} } /* This method has to be synchronized because it is called from sendMessageTick() as well as from loadSequenceToNative(). */ private synchronized void enqueueMessage(MidiMessage message, long lTick) { m_playbackAlsaMidiOut.enqueueMessage(message, lTick); } /** Put a message into the queue. This is Claus-Dieter's special method: it puts the message to the ALSA queue for delivery at the specified time. The time has to be given in ticks according to the resolution of the currently active Sequence. For this method to work, the Sequencer has to be started. The message is delivered the same way as messages from a Sequence, i.e. to all registered Transmitters. If the current queue position (as returned by getTickPosition()) is already behind the desired schedule time, the message is ignored. @param message the MidiMessage to put into the queue. @param lTick the desired schedule time in ticks. */ public void sendMessageTick(MidiMessage message, long lTick) { enqueueMessage(message, lTick); } private void startQueue() { controlQueue(AlsaSeq.SND_SEQ_EVENT_START); } private void continueQueue() { controlQueue(AlsaSeq.SND_SEQ_EVENT_CONTINUE); } private void stopQueue() { controlQueue(AlsaSeq.SND_SEQ_EVENT_STOP); } private void controlQueue(int nType) { int nSourcePort = getPlaybackPort(); int nQueue = getQueue(); sendQueueControlEvent( nType, AlsaSeq.SND_SEQ_TIME_STAMP_REAL | AlsaSeq.SND_SEQ_TIME_MODE_REL, 0, AlsaSeq.SND_SEQ_QUEUE_DIRECT, 0L, nSourcePort, AlsaSeq.SND_SEQ_CLIENT_SYSTEM, AlsaSeq.SND_SEQ_PORT_SYSTEM_TIMER, nQueue, 0, 0); } /** Send a real time START event to the subscribers immediately. */ private void sendStartEvent() { sendRealtimeEvent(AlsaSeq.SND_SEQ_EVENT_START); } /** Send a real time STOP event to the subscribers immediately. */ private void sendStopEvent() { sendRealtimeEvent(AlsaSeq.SND_SEQ_EVENT_STOP); } private void sendRealtimeEvent(int nType) { sendQueueControlEvent( nType, AlsaSeq.SND_SEQ_TIME_STAMP_REAL | AlsaSeq.SND_SEQ_TIME_MODE_REL, 0, // tag AlsaSeq.SND_SEQ_QUEUE_DIRECT, // queue 0L, // time getPlaybackPort(), // source AlsaSeq.SND_SEQ_ADDRESS_SUBSCRIBERS, // dest client AlsaSeq.SND_SEQ_ADDRESS_UNKNOWN, // dest port 0, 0, 0); } // NOTE: also used for setting position and start/stop RT private void sendQueueControlEvent( int nType, int nFlags, int nTag, int nQueue, long lTime, int nSourcePort, int nDestClient, int nDestPort, int nControlQueue, int nControlValue, long lControlTime) { m_queueControlEvent.setCommon(nType, nFlags, nTag, nQueue, lTime, 0, nSourcePort, nDestClient, nDestPort); m_queueControlEvent.setQueueControl(nControlQueue, nControlValue, lControlTime); getPlaybackAlsaSeq().eventOutputDirect(m_queueControlEvent); } private void sendAllNotesOffEvent(int nChannel) { int nSourcePort = getPlaybackPort(); m_allNotesOffEvent.setCommon( AlsaSeq.SND_SEQ_EVENT_CONTROLLER, AlsaSeq.SND_SEQ_TIME_STAMP_REAL | AlsaSeq.SND_SEQ_TIME_MODE_REL, 0, // tag AlsaSeq.SND_SEQ_QUEUE_DIRECT, // queue 0L, // time 0, nSourcePort, // source AlsaSeq.SND_SEQ_ADDRESS_SUBSCRIBERS, // dest client AlsaSeq.SND_SEQ_ADDRESS_UNKNOWN); // dest port m_allNotesOffEvent.setControl(nChannel, 0x78, 0); getPlaybackAlsaSeq().eventOutputDirect(m_allNotesOffEvent); } private void sendAllNotesOff() { // TODO: check if [0..15] or [1..16] for (int nChannel = 0; nChannel < 16; nChannel++) { sendAllNotesOffEvent(nChannel); } } /** Receive a correctely timestamped event. This method expects that the timestamp is in ticks, appropriate for the Sequence currently running. */ private void receiveTimestamped(MidiMessage message, long lTimestamp) { if (isRecording()) { // TODO: this is hacky; should implement correct track mapping Track track = m_track; MidiEvent event = new MidiEvent(message, lTimestamp); track.add(event); } // TODO: entering an event into the sequence } /** Receive an event from a Receiver. This method is called by AlsaSequencer.AlsaSequencerReceiver on receipt of a MidiMessage. */ protected void receive(MidiMessage message, long lTimestamp) { lTimestamp = getTickPosition(); receiveTimestamped(message, lTimestamp); } /////////////////////////////////////////////////// public Receiver getReceiver() throws MidiUnavailableException { return new AlsaSequencerReceiver(); } public Transmitter getTransmitter() throws MidiUnavailableException { return new AlsaSequencerTransmitter(); }/////////////////// INNER CLASSES ////////////////////////////////////// private class PlaybackAlsaMidiInListener implements AlsaMidiIn.AlsaMidiInListener { public void dequeueEvent(MidiMessage message, long lTimestamp) { if (TDebug.TraceSequencer) { TDebug.out("AlsaSequencer.PlaybackAlsaMidiInListener.dequeueEvent(): begin"); } if (TDebug.TraceSequencer) { TDebug.out("AlsaSequencer.PlaybackAlsaMidiInListener.dequeueEvent(): message: " + message); } if (message instanceof MetaMessage) { MetaMessage metaMessage = (MetaMessage) message; if (metaMessage.getType() == 0x51) // set tempo { byte[] abData = metaMessage.getData(); int nTempo = MidiUtils.getUnsignedInteger(abData[0]) * 65536 + MidiUtils.getUnsignedInteger(abData[1]) * 256 + MidiUtils.getUnsignedInteger(abData[2]); setTempoInMPQ((float) nTempo); } } // passes events to the receivers sendImpl(message, -1L); // calls control and meta listeners notifyListeners(message); if (TDebug.TraceSequencer) { TDebug.out("AlsaSequencer.PlaybackAlsaMidiInListener.dequeueEvent(): end"); } } } private class RecordingAlsaMidiInListener implements AlsaMidiIn.AlsaMidiInListener { public void dequeueEvent(MidiMessage message, long lTimestamp) { if (TDebug.TraceSequencer) { TDebug.out("AlsaSequencer.RecordingAlsaMidiInListener.dequeueEvent(): begin"); } if (TDebug.TraceSequencer) { TDebug.out("AlsaSequencer.RecordingAlsaMidiInListener.dequeueEvent(): message: " + message); } AlsaSequencer.this.receiveTimestamped(message, lTimestamp); if (TDebug.TraceSequencer) { TDebug.out("AlsaSequencer.RecordingAlsaMidiInListener.dequeueEvent(): end"); } } } private class AlsaSequencerReceiver extends TReceiver implements AlsaReceiver { public AlsaSequencerReceiver() { super(); } /** Subscribe to the passed port. * This establishes a subscription in the ALSA sequencer * so that the device this Receiver belongs to receives * event from the client:port passed as parameters. * * @return true if subscription was established, * false otherwise */ public boolean subscribeTo(int nClient, int nPort) { try { AlsaSeqPortSubscribe portSubscribe = new AlsaSeqPortSubscribe(); portSubscribe.setSender(nClient, nPort); portSubscribe.setDest(AlsaSequencer.this.getRecordingClient(), AlsaSequencer.this.getRecordingPort()); portSubscribe.setQueue(AlsaSequencer.this.getQueue()); portSubscribe.setExclusive(false); portSubscribe.setTimeUpdate(true); portSubscribe.setTimeReal(false); AlsaSequencer.this.getRecordingAlsaSeq().subscribePort(portSubscribe); portSubscribe.free(); return true; } catch (RuntimeException e) { if (TDebug.TraceAllExceptions) { TDebug.out(e); } return false; } } } private class AlsaSequencerTransmitter extends TTransmitter { private boolean m_bReceiverSubscribed; public AlsaSequencerTransmitter() { super(); m_bReceiverSubscribed = false; } public void setReceiver(Receiver receiver) { super.setReceiver(receiver); /* * Try to establish a subscription of the Receiver * to the ALSA seqencer client of the device this * Transmitter belongs to. */ if (receiver instanceof AlsaReceiver) { // TDebug.out("AlsaSequencer.AlsaSequencerTransmitter.setReceiver(): trying to establish subscription"); m_bReceiverSubscribed = ((AlsaReceiver) receiver).subscribeTo(getPlaybackClient(), getPlaybackPort()); // TODO: similar subscription for the sequencer's own midi in listener!! // this is necessary because sync messages are sent via the recording port m_bReceiverSubscribed = ((AlsaReceiver) receiver).subscribeTo(getRecordingClient(), getRecordingPort()); // TDebug.out("AlsaSequencer.AlsaSequencerTransmitter.setReceiver(): subscription established: " + m_bReceiverSubscribed); } } public void send(MidiMessage message, long lTimeStamp) { /* * Send message via Java methods only if no * subscription was established. If there is a * subscription, the message is routed inside of * the ALSA sequencer. */ if (! m_bReceiverSubscribed) { super.send(message, lTimeStamp); } } public void close() { super.close(); // TODO: remove subscription } } private class LoaderThread extends Thread { private long m_lLoadingPosition; public void run() { if (TDebug.TraceSequencer) { TDebug.out("AlsaSequencer.run(): begin"); } while (isOpen()) { do { synchronized (this) { try { this.wait(); } catch (InterruptedException e) { if (TDebug.TraceAllExceptions) { TDebug.out(e); } } } } while (! isRunning()); loadSequenceToNative(); } if (TDebug.TraceSequencer) { TDebug.out("AlsaSequencer.run(): end"); } } private void loadSequenceToNative() { if (TDebug.TraceSequencer) { TDebug.out("AlsaSequencer.loadSequenceToNative(): begin"); } Sequence sequence = getSequence(); Track[] aTracks = sequence.getTracks(); int[] anTrackPositions = new int[aTracks.length]; for (int i = 0; i < aTracks.length; i++) { anTrackPositions[i] = 0; } // this is used to get a useful tick value for the end of track message m_lLoadingPosition = 0; while (isRunning()) { boolean bTrackPresent = false; long lBestTick = Long.MAX_VALUE; int nBestTrack = -1; for (int nTrack = 0; nTrack < aTracks.length; nTrack++) { if (anTrackPositions[nTrack] < aTracks[nTrack].size()) { bTrackPresent = true; MidiEvent event = aTracks[nTrack].get(anTrackPositions[nTrack]); long lTick = event.getTick(); if (lTick < lBestTick) { lBestTick = lTick; nBestTrack = nTrack; } } } if (!bTrackPresent) { /* No more events; send end-of-track event. */ MetaMessage metaMessage = new MetaMessage(); try { metaMessage.setMessage(0x2F, new byte[0], 0); } catch (InvalidMidiDataException e) { if (TDebug.TraceAllExceptions) { TDebug.out(e); } } if (TDebug.TraceSequencer) { TDebug.out("AlsaSequencer.loadSequenceToNative(): sending End of Track message with tick " + (m_lLoadingPosition + 1)); } enqueueMessage(metaMessage, m_lLoadingPosition + 1); // leave the while (isRunning())-loop break; } /** The normal case: deliver the event found to be the next. */ MidiEvent event = aTracks[nBestTrack].get(anTrackPositions[nBestTrack]); anTrackPositions[nBestTrack]++; MidiMessage message = event.getMessage(); long lTick = event.getTick(); m_lLoadingPosition = Math.max(m_lLoadingPosition, lTick); if (message instanceof MetaMessage && ((MetaMessage) message).getType() == 0x2F) { if (TDebug.TraceSequencer) { TDebug.out("AlsaSequencer.loadSequenceToNative(): ignoring End of Track message with tick " + lTick); } } else { if (TDebug.TraceSequencer) { TDebug.out("AlsaSequencer.loadSequenceToNative(): enqueueing event with tick " + lTick); } enqueueMessage(message, lTick); } } if (TDebug.TraceSequencer) { TDebug.out("AlsaSequencer.loadSequenceToNative(): end"); } } } // TODO: start/stop; on/off private class MasterSynchronizer extends Thread { public void run() { if (TDebug.TraceSequencer) { TDebug.out("AlsaSequencer.MasterSynchronizer.run(): begin"); } while (isOpen()) { do { synchronized (this) { try { this.wait(); } catch (InterruptedException e) { if (TDebug.TraceAllExceptions) { TDebug.out(e); } } } } while (! isRunning()); double dTickMin = getTickPosition(); double dTickMax = getSequence().getTickLength(); double dTickStep = getSequence().getResolution() / 24.0; if (TDebug.TraceSequencer) { TDebug.out("MasterSynchronizer.run(): tick step: " + dTickStep); } double dTick = dTickMin; // TODO: ... && getS.Mode().equals(...) while (dTick < dTickMax && isRunning()) { long lTick = Math.round(dTick); if (TDebug.TraceSequencer) { TDebug.out("MasterSynchronizer.run(): sending clock event with tick " + lTick); } m_clockEvent.setTimestamp(lTick); getRecordingAlsaSeq().eventOutput(m_clockEvent); getRecordingAlsaSeq().drainOutput(); dTick += dTickStep; } } if (TDebug.TraceSequencer) { TDebug.out("AlsaSequencer.MasterSynchronizer.run(): end"); } } }}/*** AlsaSequencer.java ***/
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -