Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Road to RubyKaigi: Making Tinny Chiptunes with ...

Road to RubyKaigi: Making Tinny Chiptunes with Ruby

Road to RubyKaigi: Making Tinny Chiptunes with Ruby
2025.04.17. RubyKaigi 2025 LT

makicamel

April 17, 2025
Tweet

More Decks by makicamel

Other Decks in Programming

Transcript

  1. Self Introduction @makicamel / Maki Kawahara Loves Ruby , beer

    , and alcoholic drinks AndPad Inc. Creater of "Road to RubyKaigi"
  2. Road to RubyKaigi A Ruby-made game by Rubyist for Rubyists

    A side-scrolling action game played in the terminal Defeat bugs and escape deadlines to reach RubyKaigi venue `gem install road_to_rubykaigi`
  3. Music macOS does not have a built-in active sound source

    by default The user must adjust system settings Inconvenient
  4. Making Music Audio device I/O APIs macOS Core Audio Windows

    Windows Audio Session API Linux Advanced Linux Sound Architecture
  5. Making Music A cross-platform library that provides audio I/O APIs

    PortAudio https://github.com/PortAudio/portaudio The Ruby binding for PortAudio ffi-portaudio https://github.com/nanki/ffi-portaudio
  6. Making Music Create a wave (audio data) 1. Pass it

    to ffi-portaudio Output it through PortAudio to an audio device 2. Play sound with Ruby! 3.
  7. Basics of Creating Waves Representative waveforms Sine wave Square wave

    Triangle wave Sawtooth wave   Square wave (waveform)  https://en.wikipedia.org/wiki/Square_wave_(waveform)
  8. Basics of Creating Waves Periodic The changes in signal amplitude

    follow a repeating pattern Frequency Number of cycles per second e.g. 444.0Hz Repeat 440 cycles per second   Square wave (waveform)  https://en.wikipedia.org/wiki/Square_wave_(waveform)
  9. Basics of Creating Waves Period The time it takes for

    one cycle   Square wave (waveform)  https://en.wikipedia.org/wiki/Square_wave_(waveform)
  10. Basics of Creating Waves Analog The amplitude of a wave

    changes continuously within a cycle Digital A continuous signal amplitude cannot be recorded Instead, record so that it appears continuous a fixed number of samples This number is "sample rate" Sample rate The number of samples per second
  11. Basics of Creating Waves How to generate waves Just collect

    the changes in the signal and repeat them
  12. Practical Approach to Creating Waves Generate the "pico-pico" sound like

    the NES The waveforms generated by the NES Square wave Triangle wave In this talk, we focus on square waves
  13. Creating Waves: Square Wave Frequency The number of cycles per

    second Time per sample: 1 second / sample rate Increment in current phase per sample: frequency / sample rate class SquareOscillator def tick(frequency:) @phase += frequency.to_f / sample_rate @phase -= 1.0 if @phase >= 1.0 # Phase is managed from 0 to 1, reset when reaching 1 @phase end
  14. Creating Waves: Square Wave Waveform generation class SquareOscillator # ...

    def generate(frequency:) # Get the current phase within the cycle phase = tick(frequency: frequency) # 1 if in the first half, -1 if in the second half phase < 0.5 ? 1.0 : -1.0 end end Collecting samples produces a square wave
  15. Creating Waves: Square Wave Passing to PortAudio class AudioEngine <

    FFI::PortAudio::Stream def process(_input, output, framesPerBuffer, _timeInfo, _statusFlags, _userData) samples = (0...framesPerBuffer).map { @oscillator.generate(frequency:) } output.write_array_of_float(samples) :paContinue end # ... The sound plays!
  16. Creating Waves Advanced Changing the signal strength abruptly generates noise

    Many high-frequency components are produced Digital-to-analog conversion
  17. Creating Waves Advanced Noise reduction Smoothing the corners   To reduce

    noise caused by abrupt signal changes, a filter is effective   but since we want "pico-pico" sound, not adopt in this talk
  18. Creating Waves Advanced When the current position in the cycle

    is at a sharp corner add offset to the signal's strength
  19. Creating Waves Advanced When the current position in the cycle

    is at a sharp corner add offset to the signal's strength Use the cosine function to draw curves like
  20. Making Music Simply pass it to the API class AudioEngine

    < FFI::PortAudio::Stream def process(_input, output, framesPerBuffer, _timeInfo, _statusFlags, _userData) samples = (0...framesPerBuffer).map { @oscillator.generate(frequency:) } output.write_array_of_float(samples) :paContinue end # ...