Ha habido algunas buenas respuestas hasta ahora, pero adoptaría un método ligeramente diferente, bastante similar al que describiste originalmente
SELECT
songsWithTags.*,
COALESCE(SUM(v.vote),0) AS votesUp,
COALESCE(SUM(1-v.vote),0) AS votesDown
FROM (
SELECT
s.*,
COLLATE(GROUP_CONCAT(st.id_tag),'') AS tags_ids
FROM Songs s
LEFT JOIN Songs_Tags st
ON st.id_song = s.id
GROUP BY s.id
) AS songsWithTags
LEFT JOIN Votes v
ON songsWithTags.id = v.id_song
GROUP BY songsWithTags.id DESC
En esto, la subconsulta es responsable de recopilar canciones con etiquetas en una fila por canción. Esto luego se une a Votos después. También opté por simplemente resumir la columna v.votes como indicó que es 1 o 0 y, por lo tanto, SUM (v.votes) sumará 1+1+1+0+0 =3 de 5 son votos a favor, mientras que SUM(1-v.vote) sumará 0+0+0+1+1 =2 de 5 son votos negativos.
Si tuviera un índice de votos con las columnas (id_song,vote), entonces ese índice se usaría para esto, por lo que ni siquiera tocaría la mesa. Del mismo modo, si tuviera un índice en Songs_Tags con (id_song, id_tag), entonces la consulta no afectaría a esa tabla.
editar solución agregada usando count
SELECT
songsWithTags.*,
COUNT(CASE WHEN v.vote=1 THEN 1 END) as votesUp,
COUNT(CASE WHEN v.vote=0 THEN 1 END) as votesDown
FROM (
SELECT
s.*,
COLLATE(GROUP_CONCAT(st.id_tag),'') AS tags_ids
FROM Songs s
LEFT JOIN Songs_Tags st
ON st.id_song = s.id
GROUP BY s.id
) AS songsWithTags
LEFT JOIN Votes v
ON songsWithTags.id = v.id_song
GROUP BY songsWithTags.id DESC