Fast Fourier Friday
In theory you can make a system of equations that generates whatever. In practice getting the functions to do something reasonable may take some effort.
#!/usr/bin/env perl
use 5.36.0;
use MIDI;
my @Midi_Events;
my $notes = 256;
my $t = 0.0;
my $dt = 0.1;
use constant {
MIDI_CHANNEL => 0,
MIDDLE_C => 60,
NOTE_DURATION => 24,
VELO => 96
};
sub compute ($t) {
MIDDLE_C +
( 12 * sin($t) ) +
( 7 * cos( $t * 3 ) ) +
( 5 * sin( $t / 2 ) )
}
sub constrain_pitch ($p) {
$p > 100 and die "pitch too high";
$p < 20 and die "pitch too low";
$p
}
sub make_note ($p) {
state $offset = 0;
state $oldpitch = -1;
if ( $p == $oldpitch ) {
$offset += NOTE_DURATION;
} else {
push @Midi_Events,
[ note_on => $offset, MIDI_CHANNEL, $p, VELO ],
[ note_off => NOTE_DURATION, MIDI_CHANNEL, $p, 0 ];
$offset = 0;
$oldpitch = $p;
}
}
for ( 1 .. $notes ) {
my $pitch = constrain_pitch( sprintf "%.f", compute($t) );
say $pitch;
make_note($pitch);
$t += $dt;
}
MIDI::Opus->new(
{ format => 0,
ticks => 96,
tracks => [ MIDI::Track->new( { events => \@Midi_Events } ) ]
}
)->write_to_file( shift // 'out.midi' );
Or much the same in LISP, if you're into that sort of thing.
(require :asdf)
; https://thrig.me/src/smolmidi.git
(asdf:load-system :smolmidi)
(defconstant +note-count+ 2048)
(defconstant +middle-c+ 60)
(defconstant +delta_time+ 0.1)
(defun compute (time)
(+ (* 12 (sin time))
(* 7 (cos (* time 3)))
(* 5 (sin (/ time 2)))
+middle-c+))
(defun constrain-pitch (pitch)
(cond ((> pitch 100) (error "pitch too high"))
((< pitch 20) (error "ptich too low")))
pitch)
(defconstant +velo+ 96)
(defconstant +note-duration+ 24)
(let ((offset 0) (oldpitch -1))
(defun make-note (track pitch)
(if (= pitch oldpitch)
(incf offset +note-duration+)
(progn
(smolmidi:note-on track offset pitch +velo+)
(smolmidi:note-off track +note-duration+ pitch 0)
(setf offset 0)
(setf oldpitch pitch)))))
(defun make-track ()
(let* ((track (smolmidi:new-track)))
(loop repeat +note-count+ with time = 0.0 do
(let ((pitch (constrain-pitch (round (compute time)))))
(format t "~a~&" pitch)
(make-note track pitch)
(incf time +delta_time+)))
track))
(defun make-tracks () (list (make-track)))
(smolmidi:write-midi-file "snc.midi" (make-tracks))
By changing the equation used and various other filters, quite different music can be produced, for instance with an updated "compute" subroutine and to filter duplicates by a wacky pitch class (mod 7; mod 12 would be more typical) instead of by raw pitch. The note duration is also good to play around with; a short duration such as 8 may better let you hear the shape of the equation as it will be traced out quite rapidly.
...
sub compute ($t) {
MIDDLE_C +
( 12 * sin($t) ) +
( 11 * sin( $t * 24 ) ) +
( 3 * cos( $t / 7 ) ) +
( 7 * cos( $t * 3 ) ) +
( 5 * sin( $t / 2 ) );
}
...
sub make_note ($p) {
state $offset = 0;
state $oldpitch = -1;
if ( ( $p % 7 ) == $oldpitch ) {
$offset += NOTE_DURATION;
} else {
push @Midi_Events,
[ note_on => $offset, MIDI_CHANNEL, $p, VELO ],
[ note_off => NOTE_DURATION, MIDI_CHANNEL, $p, 0 ];
$offset = 0;
$oldpitch = $p % 7;
}
}
...
Or how about some whole tone action?