특정 주파수의 음을 사인파(Sine Wave)로 재생하고 싶다면, javax.sound.sampled.*
라이브러리를 사용합니다. javax 시리즈는 자바에서 기본 제공됩니다.
1) new AudioFormat
으로 새로운 오디오 형식 생성
public AudioFormat(float sampleRate:샘플수, int sampleSizeInBits:비트당샘플사이즈, int channels:채널수, boolean signed:부호여부, boolean bigEndian:빅엔디안여부)
2) 생성한 AudioFormat
에서 소스 데이터 라인(Source Data Line)을 추출 후 SourceDataLine
자료형 변수에 저장
AudioFormat af = new AudioFormat(SAMPLE_RATE, BITS, CHANNELS, IS_BIG_ENDIAN); SourceDataLine sdl = AudioSystem.getSourceDataLine(af);
3) sdl 오픈(open
) 및 시작(start
)
sdl.open(af); sdl.start();
4) 재생 정보에 대한 byte
형 배열 생성 후 해당 배열을 sld.write()
를 이용하여 기록
// Speicify playback time from SAMPLE_RATE int sizePerMs = (int)(SAMPLE_RATE / 1000); for(int i = 0; i < ms * sizePerMs; i++) { double angle = i / (SAMPLE_RATE / hz) * 2.0 * Math.PI; buf[0] = (byte) (Math.sin(angle) * 127.0 * vol); sdl.write(buf, 0, 1); }
배열에는 특정 주파수에 대한 사인파 정보를 재생시간(ms) * ms당 사이즈
만큼 기록합니다. i
가 1이고, 샘플수가 44000, 주파수는 440인 경우 angle
은 0.0628이며, 이것에 대한 사인(sine)각도을 구하면 약 13.92입니다. 구해진 사인각은 그래프에서 y축의 높이 정보에 활용됩니다(자세한 사항은 푸리에 변환 참조). 여기에 127
을 곱하는 이유는 바이트형 배열의 범위가 -128
~127
(참고사이트)이기 때문입니다. vol
(볼륨)의 기본값은 1.0입니다. 재생시간이 1초(1000ms)인 경우, 44000 / 1000 * 1000 으로 i
는 43999까지 진행합니다.
5) sd의 버퍼를 비우고(drain
) 정지(stop
), 닫기(close
) 실행
sdl.drain(); sdl.stop(); sdl.close();
drain()
을 하지 않으면 소리가 재생되지 않습니다.
추가 예제: 제이콥 콜리어(Jaco Collier)의 미분음 게임 (Microtonal Game)
위 영상의 과정을 프로그래밍으로 나타내 봤습니다. 미 ~ 솔을 일정 수로 2등분부터 8등분까지 나눠서 노래를 부르고 있는데 이 사람은 외계인이라 가능한거고 다른 사람들은 절대 이런 식으로 할 수 없습니다. 원래는 평균율 기준으로 미
– 파
– 파#
– 솔
로 3등분입니다.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package blog.freq; | |
import java.util.ArrayList; | |
import java.util.List; | |
import javax.sound.sampled.AudioFormat; | |
import javax.sound.sampled.AudioSystem; | |
import javax.sound.sampled.LineUnavailableException; | |
import javax.sound.sampled.SourceDataLine; | |
public class Freq { | |
public static float SAMPLE_RATE = 8000f; | |
private AudioFormat af; | |
private SourceDataLine sdl; | |
// Constructor | |
public Freq() throws LineUnavailableException { | |
af = new AudioFormat(SAMPLE_RATE, 8, 1, true, false); | |
sdl = AudioSystem.getSourceDataLine(af); | |
} | |
public void tone(double hz, double ms) throws LineUnavailableException{ | |
tone(hz, ms, 1.0); | |
} | |
public void tone(double hz, double ms, double vol) throws LineUnavailableException { | |
byte[] buf = new byte[1]; | |
sdl.open(af); | |
sdl.start(); | |
// Speicify playback time from SAMPLE_RATE | |
int sizePerMs = (int)(SAMPLE_RATE / 1000); | |
for(int i = 0; i < ms * sizePerMs; i++) { | |
double angle = i / (SAMPLE_RATE / hz) * 2.0 * Math.PI; | |
buf[0] = (byte) (Math.sin(angle) * 127.0 * vol); | |
sdl.write(buf, 0, 1); | |
} | |
sdl.drain(); | |
} | |
public void tones(List<Double> freqs, double ms) throws LineUnavailableException { | |
tones(freqs, ms, 1.0); | |
} | |
public void tones(List<Double> freqs, double ms, double vol) throws LineUnavailableException { | |
byte[] buf = new byte[1]; | |
sdl.open(af); | |
sdl.start(); | |
// Speicify playback time from SAMPLE_RATE | |
int sizePerMs = (int)(SAMPLE_RATE / 1000); | |
for(double hz : freqs) { | |
for(int i = 0; i < ms * sizePerMs; i++) { | |
double angle = i / (SAMPLE_RATE / hz) * 2.0 * Math.PI; | |
buf[0] = (byte) (Math.sin(angle) * 127.0 * vol); | |
sdl.write(buf, 0, 1); | |
} | |
} | |
sdl.drain(); | |
} | |
public void close() { | |
sdl.stop(); | |
sdl.close(); | |
} | |
public static void main(String[] args) throws Exception { | |
Freq f = new Freq(); | |
// double a4 = 452; | |
// for(int i = 1; i <= 16; i++) { | |
// System.out.println(a4 / 6.727 * i); | |
// f.tone(a4 / 6.727 * i, 500); | |
// } | |
System.out.println("== Microtonal Game (Ascending) ==\n"); | |
double startFreq = 329.63, // E4 ~ G4 | |
endFreq = 392; | |
int startDiv = 2, endDiv = 8; | |
for(int i = startDiv; i <= endDiv; i++) { | |
System.out.printf("–[%d]–\n", i); | |
double eachFreq = (endFreq – startFreq) / i; | |
List<Double> freqs = new ArrayList<Double>(); | |
for(int j = 0; j <= i; j++) { | |
double freq = startFreq + eachFreq * j; | |
freqs.add(freq); | |
} | |
// Display each frequency on time | |
new Thread(() -> { | |
try { | |
for(Double hz : freqs) { | |
System.out.printf("%.2f\t", hz); | |
Thread.sleep(200); | |
} | |
}catch(InterruptedException e) {} | |
}).start(); | |
f.tones(freqs, 200); | |
Thread.sleep(500); | |
System.out.print("\n\n"); | |
} | |
} | |
} |
3개의 댓글
KIM TAE WOONG · 2021년 3월 18일 10:23 오전
검색하다 좋은정보 얻고갑니다.
그리고 자바에서 mp3를 1초단위든 5초단위든 볼륨크기를 출력 할 수 있을까요? 열심이 검색중인데 잘 보이지 않네요
yoonbumtae (BGSMM) · 2021년 3월 18일 4:39 오후
죄송합니다 저도 잘 모르겠습니다..
외부 라이브러리를 사용한 방법이라면
http://yoonbumtae.com/?p=865
이 글이 음악 파일의 시간별 주파수를 분석하는 방법인데
주파수 대신 볼륨 정보를 얻는 것으로 가능하지 않을까 합니다.
자바스크립트 예제: 특정 주파수의 소리 재생 + 음악 평균율 주파수 테이블 - BGSMM · 2019년 11월 4일 12:35 오전
[…] 주파수를 재생하는 글 자바 예제: 특정 주파수의 소리 재생 을 자바스크립트로 바꾼 예제입니다. 외부 온라인 소스나 라이브러리 […]