Для удобства ввел понятие нота, она инкапсулирует в себе то число, которое я назвал тональностью - оно является кодом ноты на midi устройстве.
public class Нота { private int тональность; public Нота(int тональность) { this.тональность = тональность; } public Нота(Нота нота) { тональность = нота.тональность(); } public int тональность() { return тональность; } }Я избавился от нот в Тональностях, потому что нелогично - ее можно сыграть от какой-то ноты, но держать для каждой из нот копию Можор, Минор и так далее - не ок...
public enum Тональность { Мажор(Тон, Тон, Полутон, Тон, Тон, Тон, Полутон), Минор(Тон, Полутон, Тон, Тон, Полутон, Тон, Тон), МинорГармонический(Тон, Полутон, Тон, Тон, Полутон, МалаяТерция, Полутон), МинорМелодический(Тон, Полутон, Тон, Тон, Тон, Тон, Полутон); private Интервал[] интервалы; private Тональность(Интервал... интервалы) { this.интервалы = интервалы; } public Нота get(Нота from, int order) { int sum = 0; for (int index = 0; index < order - 1; index++) { sum += 2*интервалы[index].интервал(); } return new Нота(from.тональность() + sum); } }По аналогии я создал понятие Трезвучие
public enum Трезвучие { Мажорное(БольшаяТерция, МалаяТерция), Минорное(МалаяТерция, БольшаяТерция), Уменьшенное(МалаяТерция, МалаяТерция), Увеличенное(БольшаяТерция, БольшаяТерция); private Интервал[] интервалы; private Трезвучие(Интервал... интервалы) { this.интервалы = интервалы; } public List<Нота> get(Нота from) { List<Нота> result = new LinkedList<Нота>(); int sum = 0; result.add(new Нота(from)); for (Интервал i : интервалы) { sum += 2*i.интервал(); result.add(new Нота(from.тональность() + sum)); } return result; } }Оно очень похоже, только его основной метод расчета дает сразу три ноты, которые потом синтезатор отыграет. Добавил понятие октав
public abstract class Октава { private List<Нота> ноты = new LinkedList<Нота>(); private Октава следующая; private Октава предыдущая; public void init(Октава предыдущая, Октава следующая, int... ноты) { this.следующая = следующая; this.предыдущая = предыдущая; for (int нота : ноты) { this.ноты.add(new Нота(нота)); } } public abstract Нота база(); public Нота get(char нота) { switch (нота) { case 'C' : return get(1); case 'D' : return get(2); case 'E' : return get(3); case 'F' : return get(4); case 'G' : return get(5); case 'A' : return get(6); case 'H' : return get(7); default: throw new IllegalArgumentException("Нет такой ноты"); } } public Нота get(int номерНоты) { return ноты.get(номерНоты - 1); } public Октава следующая() { return следующая; } public Октава предыдущая() { return предыдущая; } }И для каждой октавы реализовал наследника
public class ПерваяОктава extends Октава { private static ПерваяОктава instance; public static ПерваяОктава get() { return (instance != null)?instance:new ПерваяОктава(); } public static Нота C4 = ПерваяОктава.нота(1); public static Нота D4 = ПерваяОктава.нота(2); public static Нота E4 = ПерваяОктава.нота(3); public static Нота F4 = ПерваяОктава.нота(4); public static Нота G4 = ПерваяОктава.нота(5); public static Нота A4 = ПерваяОктава.нота(6); public static Нота H4 = ПерваяОктава.нота(7); private static Нота нота(int номерНоты) { return get().get(номерНоты); } public ПерваяОктава() { instance = this; init(МалаяОктава.get(), ВтораяОктава.get(), 48, 50, 52, 53, 55, 57, 59); } @Override public Нота база() { return get(1); } }Избыточно немного, но пусть будет. Не пригодится потом удалю... А так я изменил синтезтор
public class Синтезатор { private MidiChannel midi; public Синтезатор(MidiChannelFactory midiFactory) { this.midi = midiFactory.get(); } public void звучать(Нота нота, int длительность, int сила) { midi.noteOn(нота.тональность(), сила); пауза(длительность); midi.noteOff(нота.тональность()); пауза(длительность); } public void звучать(Нота нота, int сила) { midi.noteOn(нота.тональность(), сила); } public void звучать(Нота отНоты, Тональность тональность, int длительность, int сила) { for (int index = 1; index <= 8; index++) { звучать(тональность.get(отНоты, index), длительность, сила); } } public void звучать(Нота отНоты, Трезвучие трезвучие, int сила) { List<Нота> аккорд = трезвучие.get(отНоты); for (Нота нота : аккорд) { звучать(нота, сила); } } private void пауза(int длительность) { try { Thread.sleep(длительность); } catch (InterruptedException e) { throw new RuntimeException(e); } } }Появился интерфейс
public interface MidiChannelFactory { MidiChannel get(); }А метод создания конкретного звучащего устройства ушло в новый класс
import javax.sound.midi.*; public class RealMidiChannelFactory implements MidiChannelFactory { @Override public MidiChannel get() { try { // init sequencer Sequencer sequencer = null; sequencer = MidiSystem.getSequencer(); sequencer.open(); // init synthesizer Synthesizer synth = MidiSystem.getSynthesizer(); synth.open(); // get channel for synthesizing: the highest numbered channel. sets it up MidiChannel[] channels = synth.getChannels(); MidiChannel midi = channels[channels.length - 1]; midi.programChange(0); midi.noteOn(0, 10); sleep(); midi.noteOff(0); return midi; } catch (MidiUnavailableException e) { throw new RuntimeException(e); } } private void sleep() { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } }Зато теперь синтезатор можно протестировать
import com.apofig.октавы.ПерваяОктава; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import javax.sound.midi.MidiChannel; import java.util.Arrays; import java.util.LinkedList; import static junit.framework.Assert.assertEquals; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.*; public class СинтезаторTest { private MidiChannel midi; private Синтезатор синтезатор; @Before public void setup() { RealMidiChannelFactory factory = mock(RealMidiChannelFactory.class); midi = mock(MidiChannel.class); when(factory.get()).thenReturn(midi); синтезатор = new Синтезатор(factory); } @Test public void shouldPlayДоМажорСПервойОктавы() { синтезатор.звучать(ПерваяОктава.get().get('C'), Тональность.Мажор, 1, 120); assertPlay(48, 50, 52, 53, 55, 57, 59, 60); } private void assertPlay(Integer... expected) { ArgumentCaptorВот как бы и все пока. Теперь можно изучать, как звучат разные аккорды, что мне было и нужно....captor = ArgumentCaptor.forClass(Integer.class); verify(midi, times(expected.length)).noteOn(captor.capture(), anyInt()); LinkedList expected1 = new LinkedList (); expected1.addAll(Arrays.asList(expected)); assertEquals(expected1.toString(), captor.getAllValues().toString()); } @Test public void shouldPlayЛяМажорСПервойОктавы() { синтезатор.звучать(ПерваяОктава.get().get('A'), Тональность.Мажор, 1, 120); assertPlay(57, 59, 61, 62, 64, 66, 68, 69); } @Test public void shouldPlayЛяМинорСПервойОктавы() { синтезатор.звучать(ПерваяОктава.get().get('A'), Тональность.Минор, 1, 120); assertPlay(57, 59, 60, 62, 64, 65, 67, 69); } @Test public void shouldPlayМажорныйАккордСПервойОктавы() { синтезатор.звучать(ПерваяОктава.get().get('C'), Трезвучие.Мажорное, 120); assertPlay(48, 55, 52); } @Test public void shouldPlayМинорныйАккордCПервойОктавы() { синтезатор.звучать(ПерваяОктава.get().get('C'), Трезвучие.Минорное, 120); assertPlay(48, 51, 55); } }
import com.apofig.октавы.ПерваяОктава; import static com.apofig.Трезвучие.Мажорное; public class Main { public static void main(String[] args) { Синтезатор синтезатор = new Синтезатор(new RealMidiChannelFactory()); // синтезатор.звучать(ПерваяОктава.get().get('C'), Мажорное, 120); синтезатор.звучать(ПерваяОктава.get().get('C'), Мажорное, 120); } }Продолжение следует... А пока вот исходники к этим трем частям (с git репозиторием).
Ух ты!))) Надо, надо применить свои 22 года обучения музыке! Саша, спасибо за пример, будем копать в эту сторону, очень интересно!))
ОтветитьУдалитьА вообще, почитав твои посты про игру на ф-но, вдруг тоже захотелось поиграть, поподбирать. Только вот пианинку я отдала недавно... Пойду хоть на гитаре побрынчу).
Если "Ух ты!" если захотелось что-то сделать - значит у меня получилось.
УдалитьПриятного аппетита!!