25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

221 lines
5.9 KiB

2 년 전
  1. import math
  2. import matplotlib.pyplot as plt
  3. import librosa.display
  4. import numpy as np
  5. # binary search
  6. import pygame
  7. def bin_search(arr, target):
  8. index = int(len(arr) / 2)
  9. min_index = 0
  10. max_index = len(arr) - 1
  11. found = False
  12. if target < arr[0]:
  13. return 0
  14. if target > arr[len(arr) - 1]:
  15. return len(arr) - 1
  16. while not found:
  17. if min_index == len(arr) - 2:
  18. return len(arr) - 1
  19. if arr[index] < target < arr[index + 1] or arr[index] == target:
  20. return index
  21. if arr[index] > target:
  22. max_index = index
  23. else:
  24. min_index = index
  25. index = int((min_index + max_index) / 2)
  26. def rotate(xy, theta):
  27. # https://en.wikipedia.org/wiki/Rotation_matrix#In_two_dimensions
  28. cos_theta, sin_theta = math.cos(theta), math.sin(theta)
  29. return (
  30. xy[0] * cos_theta - xy[1] * sin_theta,
  31. xy[0] * sin_theta + xy[1] * cos_theta
  32. )
  33. def translate(xy, offset):
  34. return xy[0] + offset[0], xy[1] + offset[1]
  35. def clamp(min_value, max_value, value):
  36. if value < min_value:
  37. return min_value
  38. if value > max_value:
  39. return max_value
  40. return value
  41. class AudioAnalyzer:
  42. def __init__(self):
  43. self.frequencies_index_ratio = 0 # array for frequencies
  44. self.time_index_ratio = 0 # array of time periods
  45. self.spectrogram = None # a matrix that contains decibel values according to frequency and time indexes
  46. def load(self, filename):
  47. time_series, sample_rate = librosa.load(filename) # getting information from the file
  48. # getting a matrix which contains amplitude values according to frequency and time indexes
  49. stft = np.abs(librosa.stft(time_series, hop_length=512, n_fft=2048*4))
  50. self.spectrogram = librosa.amplitude_to_db(stft, ref=np.max) # converting the matrix to decibel matrix
  51. frequencies = librosa.core.fft_frequencies(n_fft=2048*4) # getting an array of frequencies
  52. # getting an array of time periodic
  53. times = librosa.core.frames_to_time(np.arange(self.spectrogram.shape[1]), sr=sample_rate, hop_length=512, n_fft=2048*4)
  54. self.time_index_ratio = len(times)/times[len(times) - 1]
  55. self.frequencies_index_ratio = len(frequencies)/frequencies[len(frequencies)-1]
  56. def show(self):
  57. librosa.display.specshow(self.spectrogram,
  58. y_axis='log', x_axis='time')
  59. plt.title('spectrogram')
  60. plt.colorbar(format='%+2.0f dB')
  61. plt.tight_layout()
  62. plt.show()
  63. def get_decibel(self, target_time, freq):
  64. return self.spectrogram[int(freq*self.frequencies_index_ratio)][int(target_time*self.time_index_ratio)]
  65. # returning the current decibel according to the indexes which found by binary search
  66. # return self.spectrogram[bin_search(self.frequencies, freq), bin_search(self.times, target_time)]
  67. def get_decibel_array(self, target_time, freq_arr):
  68. arr = []
  69. for f in freq_arr:
  70. arr.append(self.get_decibel(target_time,f))
  71. return arr
  72. class AudioBar:
  73. def __init__(self, x, y, freq, color, width=50, min_height=10, max_height=100, min_decibel=-80, max_decibel=0):
  74. self.x, self.y, self.freq = x, y, freq
  75. self.color = color
  76. self.width, self.min_height, self.max_height = width, min_height, max_height
  77. self.height = min_height
  78. self.min_decibel, self.max_decibel = min_decibel, max_decibel
  79. self.__decibel_height_ratio = (self.max_height - self.min_height)/(self.max_decibel - self.min_decibel)
  80. def update(self, dt, decibel):
  81. desired_height = decibel * self.__decibel_height_ratio + self.max_height
  82. speed = (desired_height - self.height)/0.1
  83. self.height += speed * dt
  84. self.height = clamp(self.min_height, self.max_height, self.height)
  85. def render(self, screen):
  86. pygame.draw.rect(screen, self.color, (self.x, self.y + self.max_height - self.height, self.width, self.height))
  87. class AverageAudioBar(AudioBar):
  88. def __init__(self, x, y, rng, color, width=50, min_height=10, max_height=100, min_decibel=-80, max_decibel=0):
  89. super().__init__(x, y, 0, color, width, min_height, max_height, min_decibel, max_decibel)
  90. self.rng = rng
  91. self.avg = 0
  92. def update_all(self, dt, time, analyzer):
  93. self.avg = 0
  94. for i in self.rng:
  95. self.avg += analyzer.get_decibel(time, i)
  96. self.avg /= len(self.rng)
  97. self.update(dt, self.avg)
  98. class RotatedAverageAudioBar(AverageAudioBar):
  99. def __init__(self, x, y, rng, color, angle=0, width=50, min_height=10, max_height=100, min_decibel=-80, max_decibel=0):
  100. super().__init__(x, y, 0, color, width, min_height, max_height, min_decibel, max_decibel)
  101. self.rng = rng
  102. self.rect = None
  103. self.angle = angle
  104. def render(self, screen):
  105. pygame.draw.polygon(screen, self.color, self.rect.points)
  106. def render_c(self, screen, color):
  107. pygame.draw.polygon(screen, color, self.rect.points)
  108. def update_rect(self):
  109. self.rect = Rect(self.x, self.y, self.width, self.height)
  110. self.rect.rotate(self.angle)
  111. class Rect:
  112. def __init__(self,x ,y, w, h):
  113. self.x, self.y, self.w, self.h = x,y, w, h
  114. self.points = []
  115. self.origin = [self.w/2,0]
  116. self.offset = [self.origin[0] + x, self.origin[1] + y]
  117. self.rotate(0)
  118. def rotate(self, angle):
  119. template = [
  120. (-self.origin[0], self.origin[1]),
  121. (-self.origin[0] + self.w, self.origin[1]),
  122. (-self.origin[0] + self.w, self.origin[1] - self.h),
  123. (-self.origin[0], self.origin[1] - self.h)
  124. ]
  125. self.points = [translate(rotate(xy, math.radians(angle)), self.offset) for xy in template]
  126. def draw(self,screen):
  127. pygame.draw.polygon(screen, (255,255, 0), self.points)