2017-04-07 24 views
-1

編輯:這是一個自包含的例子:停止MIDI音序器,這樣我可以打別的

MidiLatte midiLatte = new MidiLatte(); 

for (int i = 60; i <= 72; i++) { 
    midiLatte.addNote(i, 4); 
} 
midiLatte.playAndRemove(); 

try { 
    Thread.sleep(3000); 
} catch (InterruptedException e) { 
    e.printStackTrace(); 
} 

try { 
    Field f = MidiLatte.class.getDeclaredField("player"); 
    f.setAccessible(true); 
    Sequencer s = (Sequencer) f.get(midiLatte); 
    s.stop(); 
} catch (NoSuchFieldException | IllegalAccessException e) { 
    e.printStackTrace(); 
} 

for (int i = 48; i <= 60; i++) { 
    midiLatte.addNote(i, 4); 
} 
midiLatte.playAndRemove(); 

我能找到的MidiLattehere的源代碼。對不起,處理它;我需要將它用於我的課程。 (如果它是由我決定的,我會直接使用​​3210 ...)這個應該做的是開始播放一個12個音符的序列(第一個for循環),但在3000毫秒後停止,這樣它可以播放不同的序列(第二個for循環)。然而,最終發生的事情是,當第二個序列開始播放時,它從一開始就不這樣做;它在開始的幾個音符。


我正在使用的​​3210 API一個Java GUI應用程序。具體來說,它是一個音序器,允許用戶輸入音符並按順序播放。我遇到的問題是,我需要一種方法,在發送完MIDI消息後,停止它們以便我可以播放其他內容。例如,讓我們說用戶點擊播放。然後,顯然,該程序播放音符。我想要發生的是,如果用戶在序列播放過程中按下播放鍵,它會停止當前播放並重新開始。

我已經獲得MIDI與Swing一起工作的方式 - 在不破壞事件隊列的情況下這麼做並不重要),而是通過獨立的Thread來處理MIDI內容。除了我在前一段中提出的問題,這工作正常。這是我Thread實施run方法:

@Override 
public void run() { 
    midiLatte = new MidiLatte(); 
    //This loop waits for tasks to become available 
    while (true) { 
     try { 
      tasks.take().accept(midiLatte); 
     } catch (InterruptedException e) { 
      System.out.println("MPThread interrupted"); 
     } 
    } 
} 

MidiLatte是一種實用工具類,可以更容易地發送MIDI信息幷包裝Sequencer。基本上,這個工作的方式是tasksConsumer<MidiLatte>LinkedBlockingQueues。這可以讓我做的是在外部類中有一個方法(MPThread,我的自定義Thread是一個內部類),它將一個Consumer<MidiLatte>添加到隊列中。這便於安排MIDI任務是這樣的:

midiPlayer.addAction(midiLatte -> { 
    // do midi stuff 
}); 

然而,如上所述,我需要能夠取消這些任務和停止MIDI回放,一旦他們已經被髮送。前者是容易我可以清除隊列:

tasks.clear(); 

然而,後者是困難的,因爲MIDI任務已經發送,並在MIDI API手中。我一直在使用sequencer.stop()嘗試,但我正在從怪異的結果,如在這個例子中:

player.addAction(midiLatte -> { 
    //midi stuff 
}); 
try { 
    Thread.sleep(3000); 
} catch (InterruptedException e) { 
    e.printStackTrace(); 
} 
player.cancelPending(); 
player.addAction(midiLatte -> { 
    //Midi stuff 
}); 

cancelPending()調用sequencer.stop()並清除隊列。最終發生的事情如下(我從經驗中知道,當用戶不清楚地描述他們的bug時,它可能會令人討厭和困惑,所以我要逐步展示它。請告訴我,如果你仍然不明白什麼發生。):

  • 第一個序列開始正確播放。
  • 經過3000(或多或少)毫秒後,第一個序列停止。
  • 第二個序列立即開始播放,但不從頭開始;而是開始玩,就好像它一直在玩。

爲了說明這一點,設想第一個序列的音符是A B C D E F G A,第二個序列是1 2 3 4 5 6 7 8。應該發生的是,如果第一個序列中的3000密爾落在CD之間,則整個回放爲A B C 1 2 3 4 5 6 7 8。但是,實際發生的是A B C 4 5 6 7 8

我該如何實現我想要的行爲?如果我設置併發的方式存在固有的問題,請告訴我。只是重申我想要的行爲,就是這樣。 當按下播放按鈕時,MIDI序列播放。如果在序列仍在播放的過程中再次按下該按鈕,則第一個播放會停止,並且序列會從頭開始再次播放。如果我不清楚,請告訴我。謝謝!

+1

爲了更好的幫助,一個[MCVE]或[簡短,獨立,正確的例子](http://www.sscce.org/)。 –

+1

我與@AndrewThompson--讓我們理解你的代碼和你的問題的最好方法是創建你的[MCVE](http://stackoverflow.com/help/mcve)和/或[SSCCE] (http://www.sscce.org/)並將其與您的問題一起發佈,但這不是一件容易的事情,因爲您將不得不嘲笑我們沒有訪問的庫至。 –

+2

我不確定這與Swing有什麼關係,所以你可能只是能夠從你的自定義播放器中生成一個例子 – MadProgrammer

回答

1

好的,儘可能多地說,問題是除了無法實際控制底層Sequencer,因爲它繼續播放它的緩存數據。

不幸的是,您無權訪問Sequencer ... yippy skippy,MidiLatte也不提供任何擴展點來修改代碼。

黑客攻擊的解決辦法是做你已經做什麼,使用反射來訪問私有字段,例如...

!這是一個黑客!

如果不能修改的MidiLatte的原始來源,那麼你別無選擇,爲了實現你彷彿要想要成功,你必須訪問Sequencer

public static class StoppableMidiLatte extends MidiLatte { 

    public void stop() { 
     removeAll(); 
     try { 
      Field f = MidiLatte.class.getDeclaredField("player"); 
      f.setAccessible(true); 
      Sequencer s = (Sequencer) f.get(this); 
      s.stop(); 
     } catch (NoSuchFieldException | IllegalAccessException e) { 
      e.printStackTrace(); 
     } 
    } 

} 

由於到併發的需要,我會說你不需要它,因爲Sequencer已經在使用它自己的線程來播放音符,併發性只會使它在這種情況下更加困難

+0

對不起,這是我的錯,因爲沒有選擇好我的榜樣,但我已經這樣做了!這就是我能夠稱呼「停止'並且遇到了我的問題,即使當我這樣做的時候,第二個序列並不是從一開始就開始的,我想通過仔細研究'MidiLatte'來嘗試解決這個問題, daemon threads。我也可能試着獲得允許拋棄'MidiLatte'並自己使用Sound API。謝謝你的時間! – ostrichofevil

+0

我還要做的一件事就是調用'removeAll',不知道它是否成功差異,但在我的測試中,我能夠讓它工作得很好 – MadProgrammer