Module: Muze::Display

Defined in:
lib/muze/display/specshow.rb

Constant Summary collapse

BASE64_ALPHABET =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

Class Method Summary collapse

Class Method Details

.onsetshow(onset_envelope, sr: 22_050, hop_length: 512, output: nil, width: 800, height: 160, normalize: true) ⇒ String

Returns SVG content.

Returns:

  • (String)

    SVG content

Raises:



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/muze/display/specshow.rb', line 92

def onsetshow(onset_envelope, sr: 22_050, hop_length: 512, output: nil, width: 800, height: 160, normalize: true)
  raise Muze::ParameterError, "width and height must be positive" unless width.positive? && height.positive?
  raise Muze::ParameterError, "sr and hop_length must be positive" unless sr.positive? && hop_length.positive?
  raise Muze::ParameterError, "normalize must be true or false" unless [true, false].include?(normalize)

  envelope = Numo::SFloat.cast(onset_envelope).to_a.flatten
  raise Muze::ParameterError, "onset envelope must contain only finite values" unless envelope.all? { |value| value.respond_to?(:finite?) && value.finite? }

  peak = envelope.map(&:abs).max.to_f
  values = normalize && peak.positive? ? envelope.map { |value| value / peak } : envelope
  width = width.to_f
  height = height.to_f
  bar_width = width / [values.length, 1].max
  bars = values.each_with_index.map do |value, index|
    scaled = [[value, 0.0].max, 1.0].min
    bar_height = scaled * height
    x = index * bar_width
    y = height - bar_height
    "<rect x='#{x.round(3)}' y='#{y.round(3)}' width='#{[bar_width, 0.1].max.round(3)}' height='#{bar_height.round(3)}' fill='#22d3ee' />"
  end

  svg = [
    "<svg xmlns='http://www.w3.org/2000/svg' width='#{width.to_i}' height='#{height.to_i}' viewBox='0 0 #{width.to_i} #{height.to_i}'>",
    "<rect width='100%' height='100%' fill='#111827' />",
    "<g data-kind='onset' data-sr='#{sr}' data-hop-length='#{hop_length}'>",
    bars.join,
    "</g>",
    "</svg>"
  ].join

  write_output(output, svg) if output
  svg
end

.specshow(data, sr: 22_050, hop_length: 512, x_axis: :time, y_axis: :linear, output: nil, width: 800, height: 400, cmap: :heat, vmin: nil, vmax: nil, fragment: false, render: :auto) ⇒ String

Returns SVG content.

Parameters:

  • data (Numo::NArray)
  • sr (Integer) (defaults to: 22_050)
  • hop_length (Integer) (defaults to: 512)
  • x_axis (Symbol) (defaults to: :time)
  • y_axis (Symbol) (defaults to: :linear)
  • output (String, nil) (defaults to: nil)

Returns:

  • (String)

    SVG content

Raises:



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/muze/display/specshow.rb', line 14

def specshow(data, sr: 22_050, hop_length: 512, x_axis: :time, y_axis: :linear, output: nil, width: 800, height: 400, cmap: :heat, vmin: nil, vmax: nil, fragment: false, render: :auto)
  validate_axis!(x_axis:, y_axis:)
  raise Muze::ParameterError, "width and height must be positive" unless width.positive? && height.positive?
  raise Muze::ParameterError, "sr and hop_length must be positive" unless sr.positive? && hop_length.positive?
  raise Muze::ParameterError, "render must be :auto, :rects, or :image" unless %i[auto rects image].include?(render)

  matrix = Numo::SFloat.cast(data)
  matrix = matrix.expand_dims(1) if matrix.ndim == 1
  validate_matrix!(matrix)
  validate_color_bounds!(vmin:, vmax:)
  render = image_render?(matrix, render:) ? :image : :rects
  matrix = downsample_matrix(matrix, max_cells: 12_000) if render == :rects
  rows, cols = matrix.shape

  width = width.to_f
  height = height.to_f
  min = vmin || matrix.min
  max = vmax || matrix.max
  range = [max - min, 1.0e-12].max

  content = render == :image ? image_element(matrix, width:, height:, min:, range:, cmap:) : rect_elements(matrix, rows:, cols:, width:, height:, min:, range:, cmap:, y_axis:, x_axis:, sr:, hop_length:)

  body = [
    "<g data-x-axis='#{x_axis}' data-y-axis='#{y_axis}' data-sr='#{sr}' data-hop-length='#{hop_length}' data-render='#{render}'>",
    content,
    "</g>"
  ].join

  svg = if fragment
          body
        else
          [
    "<svg xmlns='http://www.w3.org/2000/svg' width='#{width.to_i}' height='#{height.to_i}' viewBox='0 0 #{width.to_i} #{height.to_i}'>",
    "<rect width='100%' height='100%' fill='#0b132b' />",
    body,
    "</svg>"
          ].join
        end

  write_output(output, svg) if output
  svg
end

.waveshow(y, sr: 22_050, output: nil, width: 800, height: 240, normalize: true, channels: :overlay) ⇒ String

Returns SVG content.

Parameters:

  • y (Numo::SFloat, Array<Float>)
  • sr (Integer) (defaults to: 22_050)
  • output (String, nil) (defaults to: nil)

Returns:

  • (String)

    SVG content

Raises:



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/muze/display/specshow.rb', line 61

def waveshow(y, sr: 22_050, output: nil, width: 800, height: 240, normalize: true, channels: :overlay)
  raise Muze::ParameterError, "width and height must be positive" unless width.positive? && height.positive?
  raise Muze::ParameterError, "sr must be positive" unless sr.positive?
  raise Muze::ParameterError, "normalize must be true or false" unless [true, false].include?(normalize)
  raise Muze::ParameterError, "channels must be :overlay or :split" unless %i[overlay split].include?(channels)

  signal = Muze::Core::Audio.validate_audio!(y, allow_empty: true).to_a
  channel_data = signal.first.is_a?(Array) ? transpose_channels(signal) : [signal]
  channel_data = channel_data.map { |channel| normalize ? normalize_wave(channel) : channel }
  width = width.to_f
  height = height.to_f
  middle = height / 2.0
  paths = channel_data.each_with_index.map do |channel, index|
    top, lane_height = channel_lane(index, channel_data.length, height, channels:)
    envelope_path(channel, width:, top:, height: lane_height)
  end

  svg = [
    "<svg xmlns='http://www.w3.org/2000/svg' width='#{width.to_i}' height='#{height.to_i}' viewBox='0 0 #{width.to_i} #{height.to_i}'>",
    "<rect width='100%' height='100%' fill='#111827' />",
    "<g data-sr='#{sr}' data-channels='#{channels}' transform='translate(0 #{middle * 0.0})'>",
    paths.join,
    "</g>",
    "</svg>"
  ].join

  write_output(output, svg) if output
  svg
end