class STFTSpectrogram::GUI

User interface

Constants

MIN_WINDOW_HEIGHT
MIN_WINDOW_WIDTH
PLOT_TYPE_SPECTROGRAM
PLOT_TYPE_TOP_MAGNITUDES

Public Class Methods

new(app) click to toggle source
Calls superclass method
# File lib/gui/gui.rb, line 17
def initialize(app)
  super(app, 'STFT Spectrogram generator', width: 1280, height: 720,
                                           opts: DECOR_ALL &
                                             ~DECOR_SHRINKABLE)

  @plot = Plot.new

  @audio = nil
  @spectrogram = nil

  vframe1 = FXVerticalFrame.new(self, width: 1280, opts: LAYOUT_FILL)
  hframe1 = FXHorizontalFrame.new(vframe1, opts: LAYOUT_FILL | FRAME_SUNKEN)
  @img_spectrum = FXImageView.new(hframe1, opts: LAYOUT_FILL)

  @current_spec_img = nil

  hframe2 = FXHorizontalFrame.new(vframe1)

  vframe_window = FXVerticalFrame.new(hframe2, opts: LAYOUT_FILL)
  hframe_window_size = FXHorizontalFrame.new(vframe_window,
                                             opts: LAYOUT_RIGHT)
  hframe_window_overlap = FXHorizontalFrame.new(vframe_window,
                                                opts: LAYOUT_RIGHT)
  FXLabel.new(hframe_window_size, 'Window size:')
  @spin_window_size = FXSpinner.new(hframe_window_size, 10)
  @spin_window_size.range = 1..(2**31 - 1)
  @spin_window_size.value = 1
  FXLabel.new(hframe_window_size, 'ms')
  FXLabel.new(hframe_window_overlap, 'Window overlap:')
  @spin_window_overlap = FXSpinner.new(hframe_window_overlap, 10)
  @spin_window_overlap.range = 0..(2**31 - 1)
  FXLabel.new(hframe_window_overlap, 'ms')

  FXVerticalSeparator.new(hframe2)

  vframe_freq = FXVerticalFrame.new(hframe2, opts: LAYOUT_FILL)
  hframe_freq_low = FXHorizontalFrame.new(vframe_freq, opts: LAYOUT_RIGHT)
  hframe_freq_high = FXHorizontalFrame.new(vframe_freq, opts: LAYOUT_RIGHT)
  FXLabel.new(hframe_freq_low, 'Low pass:')
  @spin_freq_low = FXSpinner.new(hframe_freq_low, 10)
  @spin_freq_low.range = 0..(2**31 - 1)
  FXLabel.new(hframe_freq_low, 'Hz')
  FXLabel.new(hframe_freq_high, 'High pass:')
  @spin_freq_high = FXSpinner.new(hframe_freq_high, 10)
  @spin_freq_high.range = 0..(2**31 - 1)
  FXLabel.new(hframe_freq_high, 'Hz')

  FXVerticalSeparator.new(hframe2)

  vframe_wav = FXVerticalFrame.new(hframe2, opts: LAYOUT_FILL)
  hframe_wav_txt = FXHorizontalFrame.new(vframe_wav, opts: LAYOUT_RIGHT)
  hframe_wav_btn = FXHorizontalFrame.new(vframe_wav, opts: LAYOUT_RIGHT)
  FXLabel.new(hframe_wav_txt, 'Loaded wave file:')
  @txt_wav_file = FXTextField.new(hframe_wav_txt, 60,
                                  opts: TEXTFIELD_READONLY)
  btn_wav_open = FXButton.new(hframe_wav_btn, 'Select WAV file')
  btn_wav_open.connect(SEL_COMMAND) { open_wav_file }

  FXVerticalSeparator.new(hframe2)

  vframe_plot = FXVerticalFrame.new(hframe2, opts: LAYOUT_FILL)
  hframe_plot_type = FXHorizontalFrame.new(vframe_plot, opts: LAYOUT_RIGHT)
  hframe_gen_plot = FXHorizontalFrame.new(vframe_plot, opts: LAYOUT_RIGHT)

  @combo_plot_type = FXComboBox.new(hframe_plot_type, 25,
                                    opts: COMBOBOX_STATIC)
  @combo_plot_type.appendItem('Spectrogram', PLOT_TYPE_SPECTROGRAM)
  @combo_plot_type.appendItem('Most significant frequencies only',
                              PLOT_TYPE_TOP_MAGNITUDES)

  btn_plot = FXButton.new(hframe_gen_plot, 'Generate plot')
  btn_plot.connect(SEL_COMMAND) { on_btnplot_pressed }

  FXVerticalSeparator.new(hframe2)

  hframe_save_img = FXHorizontalFrame.new(hframe2, opts: LAYOUT_RIGHT)
  btn_save_img = FXButton.new(hframe_save_img, 'Save graph')
  btn_save_img.connect(SEL_COMMAND) { on_save_graph }

  @spin_window_size.connect(SEL_COMMAND) { on_window_changed }
  @spin_window_overlap.connect(SEL_COMMAND) { on_window_changed }
end

Public Instance Methods

create() click to toggle source
Calls superclass method
# File lib/gui/gui.rb, line 100
def create
  super
  show(PLACEMENT_SCREEN)
end

Private Instance Methods

create_spectrogram() click to toggle source
# File lib/gui/gui.rb, line 161
def create_spectrogram
  return nil if @audio.nil?
  return @spectrogram unless @spectrogram.nil?
  spectrogram = nil
  begin
    spectrogram = Spectrogram.new(@audio, @spin_window_size.value,
                                  @spin_window_overlap.value)
  rescue ArgumentError => e
    FXMessageBox.error(self, MBOX_OK, 'Bad arguments!', e.message)
    return nil
  end
  spectrogram
end
load_data(path) click to toggle source
# File lib/gui/gui.rb, line 147
def load_data(path)
  audio = nil
  begin
    audio = AudioFile.new(path)
  rescue IOError => e
    FXMessageBox.error(self, MBOX_OK, 'Non-existing file!', e.message)
    return nil
  rescue WaveFile::FormatError => e
    FXMessageBox.error(self, MBOX_OK, 'Bad format!', e.message)
    return nil
  end
  audio
end
load_generated_image(path) click to toggle source
# File lib/gui/gui.rb, line 197
def load_generated_image(path)
  @current_spec_img = FXPNGImage.new(getApp, nil, IMAGE_KEEP |
                                       IMAGE_OWNED | IMAGE_SHMP |
                                       IMAGE_SHMI)
  getApp.beginWaitCursor do
    FXFileStream.open(path, FXStreamLoad) do |stream|
      @current_spec_img.loadPixels(stream)
    end
    @current_spec_img.create
    @img_spectrum.image = @current_spec_img
  end
end
no_data?(data) click to toggle source
# File lib/gui/gui.rb, line 210
def no_data?(data)
  if data.empty?
    FXMessageBox.error(self, MBOX_OK, 'No data!',
                       'Selected slice contains no data!')
    return true
  end
  false
end
on_btnplot_pressed() click to toggle source
# File lib/gui/gui.rb, line 179
def on_btnplot_pressed
  @spectrogram = create_spectrogram
  return if @spectrogram.nil?
  @spectrogram.transform! unless @spectrogram.transformed?
  @spectrogram.filter(@spin_freq_low.value, @spin_freq_high.value)

  index = @combo_plot_type.currentItem
  return if index < 0

  type = @combo_plot_type.getItemData(index)

  if type == PLOT_TYPE_SPECTROGRAM
    plot_spectrogram
  elsif type == PLOT_TYPE_TOP_MAGNITUDES
    plot_top_freqs
  end
end
on_save_graph() click to toggle source
# File lib/gui/gui.rb, line 126
def on_save_graph
  if @current_spec_img.nil?
    FXMessageBox.error(self, MBOX_OK, 'No image!',
                       'Generate the graph first!')
    return
  end
  dialog = FXFileDialog.new(self, 'Save Graph')
  dialog.patternList = ['PNG Files (*.png)']
  dialog.filename = '*.png'
  unless dialog.execute.zero?
    if File.exist? dialog.filename
      overwrite = FXMessageBox.question(self, MBOX_YES_NO,
                                        'File exists!',
                                        'Overwrite file?')
      return 1 if overwrite == MBOX_CLICKED_NO
    end
    save_graph(dialog.filename)
  end
  1
end
on_window_changed() click to toggle source
# File lib/gui/gui.rb, line 175
def on_window_changed
  @spectrogram = nil
end
open_wav_file() click to toggle source
# File lib/gui/gui.rb, line 107
def open_wav_file
  dialog = FXFileDialog.new(self, 'Open WAV File')
  dialog.patternList = ['WAV Files (*.wav)']
  dialog.selectMode = SELECTFILE_EXISTING
  return if dialog.execute.zero?
  @txt_wav_file.text = ''
  @spectrogram = nil
  @audio = load_data(dialog.filename)
  @txt_wav_file.text = dialog.filename unless @audio.nil?
end
plot_spectrogram() click to toggle source
# File lib/gui/gui.rb, line 230
def plot_spectrogram
  freqs = @spectrogram.freqs
  return if no_data? freqs
  data = @spectrogram.windows.map { |w| w.spectrum.values }

  x = @spectrogram.windows.map { |w| w.time.to_i }.uniq
  len = data.length / x.length
  xmap = x.map.with_index do |t, i|
    '"' + t.to_s + '" ' + (i * len).to_s
  end
  len = x.length / [x.length, 15].min
  xmap = (len - 1).step(xmap.size - 1, len).map { |i| xmap[i] }
  data = data.transpose
  len = data.length / freqs.length
  ymap = freqs.map.with_index do |f, i|
    '"' + (f / 1000.0).to_s + '" ' + (i * len).to_s
  end
  len = freqs.length / [freqs.length, 20].min
  ymap = (len - 1).step(ymap.size - 1, len).map { |i| ymap[i] }

  @plot.xtics = '(' + xmap.join(', ') + ')'
  @plot.ytics = '(' + ymap.join(', ') + ')'
  @plot.plot_matrix(data, @img_spectrum.width, @img_spectrum.height)

  load_generated_image(@plot.imgfile.path)
end
plot_top_freqs() click to toggle source
# File lib/gui/gui.rb, line 219
def plot_top_freqs
  freqs = @spectrogram.freqs
  return if no_data? freqs
  y = @spectrogram.windows.map { |w| w.strongest_freq[0] / 1000.0 }
  x = @spectrogram.windows.map(&:time)
  @plot.xtics = 'auto'
  @plot.ytics = 'auto'
  @plot.plot(x, y, @img_spectrum.width, @img_spectrum.height)
  load_generated_image(@plot.imgfile.path)
end
save_graph(path) click to toggle source
# File lib/gui/gui.rb, line 118
def save_graph(path)
  getApp.beginWaitCursor do
    FXFileStream.open(path, FXStreamSave) do |stream|
      @current_spec_img.savePixels(stream)
    end
  end
end