Для удобства ввел понятие нота, она инкапсулирует в себе то число, которое я назвал тональностью - оно является кодом ноты на 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 года обучения музыке! Саша, спасибо за пример, будем копать в эту сторону, очень интересно!))
ОтветитьУдалитьА вообще, почитав твои посты про игру на ф-но, вдруг тоже захотелось поиграть, поподбирать. Только вот пианинку я отдала недавно... Пойду хоть на гитаре побрынчу).
Если "Ух ты!" если захотелось что-то сделать - значит у меня получилось.
УдалитьПриятного аппетита!!