피아노 앱을 만들려고 midi 파일도 들여다 보고, 좀 더 좋은 소리를 내려면 어떻게 해야하나 연구도 하고, 안드로이드에서 악기 앱은 어떤식으로 구현하는지 보고 있네요.
보통의 악기 앱들은 midi 파일을 불러다가 출력하여 소리를 내도록 되어 있는거 같습니다. 그것도 동적으로 미디 파일을 생성하는것이 아니라 프로그램이 시작시 모든 음과 소리를 파일로 만들어 두고 사용자의 제스처에 반응하도록 만드는 방식으로요.
올려드리는 파일은 java 로 작성된 실제 미디 파일을 만들어 루트 디렉토리에 test1.mid 라는 파일을 생성해줍니다.
이 파일을 안드로이드에서 연주하면 물론 아주 좋은 소리 잘 나옵니다. MIDI 파일 구조에 대해 조금은 아셔야 하지만 우선 미디파일을 생성하는 방법만 알아두셔도 앞으로 공부하는데 지장은 없으실것 같고요. 맨위에 만든 분 이름이 있습니다.
출처 알려드립니다. http://kevinboone.net/javamidi.html 가시면 상세한 설명 보실 수 있습니다.
전체 소스 아래에 올리고 파일로 올려드립니다.
자바 파일 :
개발자의 공유는 1만줄의 코드보다 소중합니다.
미디파일 분석자료 다른 분이 잘 만드신거 올립니다. 출처 : http://blog.daum.net/jty71/15645096
이분 자료를 워드파일로 만든것입니다. 미디파일 분석하시는 분 참고하세요~
왕초보 MIDI File Format 분석법2.docx
/*
A simple Java class that writes a MIDI file
(c)2011 Kevin Boone, all rights reserved
*/
package kim;
import java.io.*;
import java.util.*;
public class MidiFile
{
// Note lengths
// We are working with 32 ticks to the crotchet. So
// all the other note lengths can be derived from this
// basic figure. Note that the longest note we can
// represent with this code is one tick short of a
// two semibreves (i.e., 8 crotchets)
static final int SEMIQUAVER = 4;
static final int QUAVER = 8;
static final int CROTCHET = 16;
static final int MINIM = 32;
static final int SEMIBREVE = 64;
// Standard MIDI file header, for one-track file
// 4D, 54... are just magic numbers to identify the
// headers
// Note that because we're only writing one track, we
// can for simplicity combine the file and track headers
static final int header[] = new int[]
{
0x4d, 0x54, 0x68, 0x64, 0x00, 0x00, 0x00, 0x06,
0x00, 0x00, // single-track format
0x00, 0x01, // one track
0x00, 0x10, // 16 ticks per quarter
0x4d, 0x54, 0x72, 0x6B
};
// Standard footer
static final int footer[] = new int[]
{
0x01, 0xFF, 0x2F, 0x00
};
// A MIDI event to set the tempo
static final int tempoEvent[] = new int[]
{
0x00, 0xFF, 0x51, 0x03,
0x0F, 0x42, 0x40 // Default 1 million usec per crotchet
};
// A MIDI event to set the key signature. This is irrelent to
// playback, but necessary for editing applications
static final int keySigEvent[] = new int[]
{
0x00, 0xFF, 0x59, 0x02,
0x00, // C
0x00 // major
};
// A MIDI event to set the time signature. This is irrelent to
// playback, but necessary for editing applications
static final int timeSigEvent[] = new int[]
{
0x00, 0xFF, 0x58, 0x04,
0x04, // numerator
0x02, // denominator (2==4, because it's a power of 2)
0x30, // ticks per click (not used)
0x08 // 32nd notes per crotchet
};
// The collection of events to play, in time order
protected Vector<int[]> playEvents;
/** Construct a new MidiFile with an empty playback event list */
public MidiFile()
{
playEvents = new Vector<int[]>();
}
/** Write the stored MIDI events to a file */
public void writeToFile (String filename)
throws IOException
{
FileOutputStream fos = new FileOutputStream (filename);
fos.write (intArrayToByteArray (header));
// Calculate the amount of track data
// _Do_ include the footer but _do not_ include the
// track header
int size = tempoEvent.length + keySigEvent.length + timeSigEvent.length
+ footer.length;
for (int i = 0; i < playEvents.size(); i++)
size += playEvents.elementAt(i).length;
// Write out the track data size in big-endian format
// Note that this math is only valid for up to 64k of data
// (but that's a lot of notes)
int high = size / 256;
int low = size - (high * 256);
fos.write ((byte) 0);
fos.write ((byte) 0);
fos.write ((byte) high);
fos.write ((byte) low);
// Write the standard metadata — tempo, etc
// At present, tempo is stuck at crotchet=60
fos.write (intArrayToByteArray (tempoEvent));
fos.write (intArrayToByteArray (keySigEvent));
fos.write (intArrayToByteArray (timeSigEvent));
// Write out the note, etc., events
for (int i = 0; i < playEvents.size(); i++)
{
fos.write (intArrayToByteArray (playEvents.elementAt(i)));
}
// Write the footer and close
fos.write (intArrayToByteArray (footer));
fos.close();
}
/** Convert an array of integers which are assumed to contain
unsigned bytes into an array of bytes */
protected static byte[] intArrayToByteArray (int[] ints)
{
int l = ints.length;
byte[] out = new byte[ints.length];
for (int i = 0; i < l; i++)
{
out[i] = (byte) ints[i];
}
return out;
}
/** Store a note-on event */
public void noteOn (int delta, int note, int velocity)
{
int[] data = new int[4];
data[0] = delta;
data[1] = 0x90;
data[2] = note;
data[3] = velocity;
playEvents.add (data);
}
/** Store a note-off event */
public void noteOff (int delta, int note)
{
int[] data = new int[4];
data[0] = delta;
data[1] = 0x80;
data[2] = note;
data[3] = 0;
playEvents.add (data);
}
/** Store a program-change event at current position */
public void progChange (int prog)
{
int[] data = new int[3];
data[0] = 0;
data[1] = 0xC0;
data[2] = prog;
playEvents.add (data);
}
/** Store a note-on event followed by a note-off event a note length
later. There is no delta value — the note is assumed to
follow the previous one with no gap. */
public void noteOnOffNow (int duration, int note, int velocity)
{
noteOn (0, note, velocity);
noteOff (duration, note);
}
public void noteSequenceFixedVelocity (int[] sequence, int velocity)
{
boolean lastWasRest = false;
int restDelta = 0;
for (int i = 0; i < sequence.length; i += 2)
{
int note = sequence[i];
int duration = sequence[i + 1];
if (note < 0)
{
// This is a rest
restDelta += duration;
lastWasRest = true;
}
else
{
// A note, not a rest
if (lastWasRest)
{
noteOn (restDelta, note, velocity);
noteOff (duration, note);
}
else
{
noteOn (0, note, velocity);
noteOff (duration, note);
}
restDelta = 0;
lastWasRest = false;
}
}
}
/** Test method — creates a file test1.mid when the class
is executed */
public static void main (String[] args)
throws Exception
{
MidiFile mf = new MidiFile();
// Test 1 — play a C major chord
// Turn on all three notes at start-of-track (delta=0)
mf.noteOn (0, 60, 127);
mf.noteOn (0, 64, 127);
mf.noteOn (0, 67, 127);
// Turn off all three notes after one minim.
// NOTE delta value is cumulative — only _one_ of
// these note-offs has a non-zero delta. The second and
// third events are relative to the first
mf.noteOff (MINIM, 60);
mf.noteOff (0, 64);
mf.noteOff (0, 67);
// Test 2 — play a scale using noteOnOffNow
// We don't need any delta values here, so long as one
// note comes straight after the previous one
mf.noteOnOffNow (QUAVER, 60, 127);
mf.noteOnOffNow (QUAVER, 62, 127);
mf.noteOnOffNow (QUAVER, 64, 127);
mf.noteOnOffNow (QUAVER, 65, 127);
mf.noteOnOffNow (QUAVER, 67, 127);
mf.noteOnOffNow (QUAVER, 69, 127);
mf.noteOnOffNow (QUAVER, 71, 127);
mf.noteOnOffNow (QUAVER, 72, 127);
// Test 3 — play a short tune using noteSequenceFixedVelocity
// Note the rest inserted with a note value of -1
int[] sequence = new int[]
{
60, QUAVER + SEMIQUAVER,
65, SEMIQUAVER,
70, CROTCHET + QUAVER,
69, QUAVER,
65, QUAVER / 3,
62, QUAVER / 3,
67, QUAVER / 3,
72, MINIM + QUAVER,
-1, SEMIQUAVER,
72, SEMIQUAVER,
76, MINIM,
};
// What the heck — use a different instrument for a change
mf.progChange (10);
mf.noteSequenceFixedVelocity (sequence, 127);
mf.writeToFile ("test1.mid");
}
}
'개발자 > Android' 카테고리의 다른 글
안드로이드 AudioRecoder 쓸때 에러메시지 - 퍼미션 줘야 됨. (3) | 2013.02.28 |
---|---|
한 프로젝트 다른 패키지의 액티비티 실행할때 매니패스트에 경로입력 (0) | 2013.02.26 |
안드로이드 스트링 비교시 주의 할 점~ 알파벳 첫글자 비교등과 같은... (0) | 2013.01.10 |
안드로이드 리스트뷰에 사전식 인덱스를 Seekbar 로 구현하기 (0) | 2013.01.07 |
안드로이드에서 쓸 수 있는 핵심 제스처 설명 (0) | 2012.12.17 |
상대 레이아웃을 사용하여 이미지와 텍스트로 안드로이드 버튼 만들기 (0) | 2012.12.13 |
안드로이드 리스트 뷰 만들어 붙이는 방법 두가지로 (0) | 2012.12.11 |
[Accessibility] Missing contentDescription attribute on image 경고 (0) | 2012.12.10 |
더욱 좋은 정보를 제공하겠습니다.~ ^^