0

I have this .sh:

#!/bin/bash

cd /home/pi/Desktop/PiFmRds/src

for f in $(ls -1 /home/pi/Desktop/Music/Acapella/*.mp3|sort -R); do sox -t mp3 "$f" -t wav -r 44100 - | sudo ./pi_fm_rds -freq 102.1 -audio - -ps WZSFM -rt "ZSFM"; done

Basically, it plays through all the files in a directory in a random order, converts them from mp3 to wav on-the-fly, and then pipes that into pifm. Pifm is a program that can broadcast on fm radio. The thing is, since I am using a forloop there is a bit of static on the radio in between each file, because since it is running the pifm command to broadcast for each individual file, it makes a bit of static between when the pifm command stops after one file and starts for the next. Is there a way to have the forloop only going on the first part of the command, which picks the random song, but not on the pifm part, which actually broadcasts? This would make it that the pifm command is always broadcasting, and the forloop only applys to the part of the command shuffling through the files.

Also, I tried this:

#!/bin/bash

cd /home/pi/Desktop/PiFmRds/src

for f in $(ls -1 /home/pi/Desktop/Music/Acapella/*.mp3|sort -R); do sox -t mp3 "$f" -t wav -r 44100 - ; done | sudo ./pi_fm_rds -freq 102.1  -audio - -ps WZSFM -rt "ZSFM"

But it stopped after playing one file.

EDIT: I have summed up the problem as this: The reason there is static between files is because the pifm command is broadcasting each file, but stops and reruns for each iteration of the forloop, so there is just static in between iterations. How can I have it cycle through each file INDIVIDUALLY (kinda necessary, or else I end up with a bunch more problems), but still have it boradcasting the whole time?

horace
  • 25
  • 4

2 Answers2

1

Firstly, read and understand Why not parse ls (and what to do instead)? - there are very good reasons why you shouldn't use ls as you're attempting to do.

If you didn't need to randomise the playlist, I'd say "just use find and its -exec option. So, instead, use find with its -print0 option, pipe the output into sort -z -R (or shuf -z), and then into xargs -0r to run a script to convert and play the files.

First create a script like this:

#!/bin/bash

pifm='/home/pi/Desktop/PiFmRds/src/pi_fm_rds'

for f in "$@"; do sox -t mp3 "$f" -t wav -r 44100 - done | sudo "$pifm" -freq 102.1 -audio - -ps WZSFM -rt "ZSFM"

This is pretty much the same as your original script except that it takes all of its filenames as command-line arguments instead of trying to parse the output of ls. It converts all of the audio filenames passed to it as arguments to WAV format with sox, and pipes the output into pi_fm_rds.

BTW, you should make an effort to make your scripts more readable by adding extra linefeeds and indentation. You can tell bash that the next line is a continuation of the current line by putting a backslash (\) at the end of the line. Or, as shown in the next script below, lines ending in a pipe character (|) are also continued on the following line without needing a \.

You'll need to configure sudo to allow the user to run /home/pi/Desktop/PiFmRds/src/pi_fm_rds as root without a password if you haven't already done so.

Save it as, e.g., /usr/local/bin/randomise-playlist.sh and make it executable with chmod +x /usr/local/bin/randomise-playlist.sh.

Then run it like this to pass it a list of filenames to convert and play:

find /home/pi/Desktop/Music/Acapella/ -type f -name '*.mp3' -print0 |
  sort -z -R |
  xargs -0 -r /usr/local/bin/randomise-playlist.sh

Alternatively, use shuf -z instead of sort -z -R - shuf's purpose is to randomise its input lines.

Because this uses NUL as the filename separator throughout the entire pipeline, it will work with filenames containing any valid character (even spaces, tabs, newlines, and shell meta-characters like & and ;).

BTW, some people prefer to always end continued lines with a \, and put the pipe | character (or || or && or whatever) at the beginning of the next line. It works exactly the same either way, so choose the method that seems more readable to you. But it's always a good idea to indent continued lines with a few spaces (I use 2 spaces, usually. 4 is good too. or sometimes I try to line up arguments so that they start in the same column on subsequent lines) or a tab to make it obvious that it's a continuation of the previous line.

find /home/pi/Desktop/Music/Acapella/ -type f -name '*.mp3' -print0 \
  | sort -z -R \
  | xargs -0 -r /usr/local/bin/randomise-playlist.sh

All of the above require the GNU versions of find, xargs, and sort (or shuf). Since you're running on a Raspberry Pi, GNU coreutils and findutils should be standard.

If you want, put that entire find ... | xargs command into its own script - it's a bit long to want to type every time you want to run it.

cas
  • 78,579
  • I need to pipe it into pifm, how do i specify an output for mp3blaster (the output would be '-')? Also, I need to convert it to wav files on the fly, can I do that? I tried looking it up, but I couldn't find much in relation to these 2 things. Thanks so much!!! – horace Jul 05 '21 at 03:40
  • ah, okay, you don't want to play the audio files on the rpi itself, you want to use pifm to broadcast it on FM. In that case, you don't want to use mp3blaster. I'll update my answer. – cas Jul 05 '21 at 04:28
  • This comes up after the first file is finished: sox WARN sox: `-' output clipped 140 samples; decrease volume? Could not rewind in audio file, terminating Terminating: cleanly deactivated the DMA engine and killed the carrier. How can I have it go through all the songs in a directory without exiting after the first file? – horace Jul 06 '21 at 07:36
  • how do I fix that, 'cause no I'm back to the original problem of my question – horace Jul 07 '21 at 08:11
  • that's a different question to this question about for loops, and one for which i have no idea. I've rarely used sox and i've never used pifm. – cas Jul 07 '21 at 09:27
0

Instead of using a for loop to play each piece individually, use the features of the sox command to concatenate them into a single play stream.

Note also that the given method to generate the list will break if there are special characters in the filenames including spaces. Also, if you have a large number of files, you may reach command line length limits. As your original command would have had both of these problems, I'll not address that completely. But a possible solution would be to map the filenames to new numbered filenames in a random order (possibly as symbolic links) and then give them to sox as a single name like -t mp3 "*.mp3" and allow sox to to do the glob expansion.

The following changes should accomplish what you need:

  • Give the file list directly to sox instead of running sox in a loop

  • Remove the -t mp3 option and let sox autodetect the file type

  • Make sure all of the .mp3 files use the same sample rate and number of channels so they can be combined into a single concatenated output stream

    sox $(ls -1 /home/pi/Desktop/Music/Acapella/*.mp3|sort -R) -t wav -r 44100 - | sudo ./pi_fm_rds -freq 102.1 -audio - -ps WZSFM -rt "ZSFM"

If any of the above fail, you may need to preprocess the files before combining them.

Your second attempt failed because audio files by default have a header, and can't be directly appended. Without checking the documentation for the pi_fm_rds command, I don't know if it is expecting that header. If it doesn't expect it, that could be the source of your static between files. In that case, an alternative would be to tell sox to emit raw audio without the header, and continue invoking it separately for each file in a for loop.

user10489
  • 6,740
  • '-r 44100' is to change the sample rate on the fly... I actually started doing this method because when I did a whole dir in the general method you mentioned I would get errors about sample rate and # of channels has to be the same, but this gave me diff problems... no way to avoid it, i guess! – horace Jul 04 '21 at 10:51
  • I really just want to avoid preprocessing, because I want to be able to dump in new files whenever I want, not process them first, – horace Jul 04 '21 at 10:59
  • Ive summed it up as this (its really hard for me to write this in a way that makes sense): The reason there is static between files is because the pifm command is broadcasting each file, but stops and reruns for each iteration of the forloop, so there is just static in between iterations. How can I have it cycle through each file INDIVIDUALLY (kinda necessary, or else I end up with a bunch more problems), but still have it boradcasting the whole time? – horace Jul 04 '21 at 11:07
  • The sox man page says all the inputs have to be the same saple rate, but it is possible the -r option fixes that too, not sure. – user10489 Jul 04 '21 at 11:42
  • I'm not sure if the static is from closing and reopening the device or if it is from the header at the start of each file. I would have to look at the docs for pi_fm_rds to be sure. – user10489 Jul 04 '21 at 11:42