As Wikipedia tells us, PHP is a general-purpose scripting language geared toward web development.

And that’s true.

For example, WordPress – CMS that powers 40% of the web, runs on PHP and JavaScript.

But there is more to it.

PHP is a handy programming language suitable for prototyping ideas quickly.

As I’ve always been interested in playing musical instruments and recording music, I’ve decided to investigate what it takes to create audio files using PHP.

This blog post will show you how to make PHP create some actual music. I’ve decided to write the code from scratch to make the process more fun and educational.

I’ve chosen WAVE audio file format because it’s relatively easy to understand. Usually, it contains uncompressed audio, so we won’t have to deal with compression algorithms.

The code

Prerequisite: please use recent PHP versions to run the script. It has been tested to work on PHP 8.0 and above.

The script can be downloaded from this Gist.

It consists of 5 PHP classes:

  • WaveFile – responsible for creating .wav files and writing the data to disk;
  • Melody – responsible for storing successions of musical tones (a.k.a. notes);
  • Note – responsible for storing the pitch and duration of a note;
  • SineWaveSynth – responsible for generating sounds we should be hearing, e.g., sine waves;
  • Track – this class manages all the other classes and exports melodies to audio tracks.

I will clarify the most obscure parts of the code.

WaveFile::create_fmt_chunk()

A regular .WAV RIFF audio file consist of a header and sample data.

The header of a .WAV file has two sub-chunks: fmt and data.

If you want to learn more about the header, please check out this article.

$this->write_long( $this->sample_rate * static::BITS_PER_SAMPLE * static::CHANNELS / 8 );

We must consider all the audio channels (if there is more than one channel).

Hence we multiply the value by static::CHANNELS.

Currently WaveFile supports mono audio only. This is because we don’t need to use stereo audio for the purpose of this blog post.

$this->write_short( static::BITS_PER_SAMPLE * static::CHANNELS / 8 );

This sets the bytes per sample value.

WaveFile::write_long(), WaveFile::write_short()

fwrite( $this->file, pack( 'v', $value ), 2 );
fwrite( $this->file, pack( 'V', $value ), 4 );    

The pack() function is being used to convert integer and string values to binary data.

This fantastic tutorial works well in its understanding (although it is written for Perl’s implementation of pack()).

WaveFile::write_sample()

$this->buffer .= pack( 's', $value );

Calling fwrite() several thousand times should be avoided as it’s a bad practice.

In my tests, implementing my custom write “buffer” has significantly increased the performance, meaning that the internal buffer isn’t very effective.

WaveFile::__destruct()

$this->flush_buffer( true );
$this->update_sizes();
fclose( $this->file );

This one is interesting.

The header must contain information about the file’s overall size and the sample data’s size.

As this can only be calculated when the file is finalized, we call the WaveFile::update_sizes() method in the WaveFile‘s destructor.

WaveFile::get_sample_value()

return sin( 2 * M_PI * $frequency * $current_time );

The frequency value should be multiplied by 2 * π so that one period of a sine wave takes exactly one second.

Melody

class Melody implements \Iterator {

The Melody class implements the \Iterator interface.

This way, we can get individual notes by simply iterating over the $melody object, i.e.:

foreach ( $melody as $note ) {
    // do stuff
}

Isn’t that elegant?

Also, the class constructor depends on the VirtualInstrument interface instead of a particular implementation of that interface, e.g., SineWaveSynth.

This way, Track can easily be used with other virtual instruments or synths which implement the VirtualInstrument interface.

Of course, you are welcome to experiment and implement your own synths/tone generators.

Track::export_to()

$sample_value = $normalized_sample_value * $max_sample_value * 0.9;

All the sample values should be multiplied by 0.9 (or another float less than 1) to not clip the signal.

La finale

It’s time to run the script.

php melody.php

Download the script (if you haven’t already done so), and execute it:

As a result, melody.wav should be generated in the same folder where your script is.

So now you can play the file and “enjoy” the melody :).

2 thoughts on “Creating music with PHP

  1. What a fun learning exercise!

    On macOS, you can use the following to generate a ringtone from your composition:

    `afconvert -f m4af melody.wav -d aac melody.m4r`

    1. > What a fun learning exercise!
      Thanks!

      > you can use the following to generate a ringtone
      That’s good to know. 👍

Comments are now closed.