1
votes

I'm trying to generate an .mp3 that can be used as a metronome (with accent/off-accent notes). I'm new to PHP streams as well as LAME. Using this documentation, I've taken a stab at it - but I don't think I'm using the streams correctly:

<?php  
// mp3=/Bottle_120_4_4_16.mp3 (Bottle 4/4 time, 120bpm - 4 measures (16 beats))
$mp3 = strtolower(str_replace('/', '', $_GET['mp3']));
$m = explode('_', $mp3);

$sound = $m[0];//bottle
$BPM = $m[1];//120
$time_a = $m[2];//4
$time_b = $m[3];//4
$nBeats = $m[4];//16 

header('Content-Type: audio/mpeg');
header('Content-Disposition:attachment;filename='.$mp3);
header('Pragma: no-cache');

$stream = fopen('php://output', 'a');

// GENERATE PCM
$sampleRate = 48000;
$channels = 1;//Mono
$bytePerSample = 2;

$bytesInSecond = ($sampleRate * $bytePerSample * $channels);

$beatLenInSec = (60 / $BPM);
if ($time_b == 8){
    $beatLenInSec = ($beatLenInSec / 2);
}

$bytesInBeat = intval($beatLenInSec * $bytesInSecond);
$bytesInBeat = (intval($bytesInBeat / $bytePerSample) * $bytePerSample);

$accentFileName = 'sound/'.$sound.'/wav/'.$sound.'-accent_ws.wav';
$noteFileName = 'sound/'.$sound.'/wav/'.$sound.'-note_ws.wav';

$PCMDataPosition = 58;
$fileA = fopen($accentFileName, 'r');
$fileB = fopen($noteFileName, 'r');

$data = '';
for ($i = 0; $i < $nBeats; $i++) {
    if (($i % $time_a) == 1){
        fseek($fileA, $PCMDataPosition);
        $data = fread($fileA, $bytesInBeat);
    } else {
        fseek($fileB, $PCMDataPosition);
        $data = fread($fileB, $bytesInBeat);
    }
    fwrite($stream, $data);
}

$lame = fopen('php://output', 'a');

// LAME ENCODE
$path = '/home/html/';

system($path.'bin/lame -r -s 48.00 -m m -b 16 - - '.$stream.' '.$lame);

fclose($stream);
fclose($lame);
?>

Are two streams necessary for what I'm doing?

1

1 Answers

1
votes

No, two streams are not needed, one is enough.

However it's not a good idea to convert the file on the standard output, it's better to use a temporary file to easily handle errors.

This is an example of how to do it (tested on Ubuntu 13.10)

<?php  

class Wav2Mp3 {
    public $mp3;
    public $BPM;
    public $sound;
    public $time_a;
    public $time_b;
    public $nBeats;
    public $bytesInBeat;

    public function __construct($mp3) {
        // mp3=/Bottle_120_4_4_16.mp3 (Bottle 4/4 time, 120bpm - 4 measures (16 beats))
        $this->mp3 = basename(strtolower($mp3));
        $m = explode('_', $this->mp3);
        if (is_array($m) && (count($m) == 5)) {
            $this->sound  = $m[0];    // bottle
            $this->BPM    = $m[1];    // 120
            $this->time_a = $m[2];    // 4
            $this->time_b = $m[3];    // 4
            $this->nBeats = $m[4];    // 16 

            if ($this->nBeats <= 0) {
                throw new Exception("Invalid Beats ({$this->nBeats})");
            }

            // GENERATE PCM
            $sampleRate = 48000;
            $channels = 1;  // Mono
            $bytePerSample = 2;
            $bytesInSecond = ($sampleRate * $bytePerSample * $channels);

            if ($this->BPM > 0) {
                $beatLenInSec = (60 / $this->BPM);
                if ($this->time_b == 8) {
                    $beatLenInSec /= 2;
                }
                $bytesInBeat = intval($beatLenInSec * $bytesInSecond);
                $this->bytesInBeat = intval($bytesInBeat / $bytePerSample) * $bytePerSample;
                //die(print_r($this,true));
            } else {
                throw new Exception("Invalid BPM ({$this->BPM})");
            }
        } else {
            throw new Exception("Invalid file name format '{$mp3}'");
        }
    }

    public function readWav($filename) {
        if ($fp = @fopen($filename, 'r')) {
            $data = FALSE;
            if (fseek($fp, 58) == 0) {
                $data = fread($fp, $this->bytesInBeat);
            } else {
                fclose($fp);
                throw new Exception("Seek failure '{$filename}'");
            }
            fclose($fp);
            if ($data === FALSE) {
                throw new Exception("Read failure '{$filename}'");
            }
            return($data);
        } else {
            throw new Exception("Open failure '{$filename}'");
        }
    }

    public function writeMp3($filename, $accent, $note) {
        $descriptorspec = array(
            0 => array("pipe", "r"),
            1 => array("file", $filename, "w"),
            2 => array("file", "/dev/null", "w")
        );
        // LAME ENCODE
        $process = proc_open("/usr/bin/lame -r -s 48.00 -m m -b 16 - -", $descriptorspec, $pipes);
        if (is_resource($process)) {
            for ($i = 0; $i < $this->nBeats; $i++) {
                fwrite($pipes[0], (($i % $this->time_a) == 1) ? $accent : $note);
            }
            foreach ($descriptorspec as $i => $dsc) {
                if (isset($pipes[$i]) && is_resource($pipes[$i])) {
                    fclose($pipes[$i]);
                }
            }
            proc_close($process);
        } else {
            throw new Exception("Process open failure");
        }
    }

    public function convert() {
        $path = 'sound/'.$this->sound.'/wav';
        $accent = $this->readWav($path.'/'.$this->sound.'-accent_ws.wav');
        $note = $this->readWav($path.'/'.$this->sound.'-note_ws.wav');
        $outfile = tempnam('/tmp', 'snd');
        $this->writeMp3($outfile, $accent, $note);
        if (file_exists($outfile) && filesize($outfile)) {
            return($outfile);
        } else {
            @unlink($outfile);
            throw new Exception("Conversion failure");
        }
    }
}


try {
    $w = new Wav2Mp3('/Bottle_120_4_4_16.mp3');
    $filename = $w->convert();
    header('Content-Type: audio/mpeg');
    header('Content-Disposition: attachment; filename='.basename($filename));
    header('Pragma: no-cache');
    readfile($filename);
    unlink($filename);
} catch (Exception $e) {
    die($e->getMessage().PHP_EOL);
}

?>

Also have a look at Convert WAV to MP3 using LAME from PHP