music.f90 Source File


This file depends on

sourcefile~~music.f90~~EfferentGraph sourcefile~music.f90 music.f90 sourcefile~envelopes.f90 envelopes.f90 sourcefile~music.f90->sourcefile~envelopes.f90 sourcefile~forsynth.f90 forsynth.f90 sourcefile~music.f90->sourcefile~forsynth.f90 sourcefile~music_common.f90 music_common.f90 sourcefile~music.f90->sourcefile~music_common.f90 sourcefile~signals.f90 signals.f90 sourcefile~music.f90->sourcefile~signals.f90 sourcefile~tape_recorder_class.f90 tape_recorder_class.f90 sourcefile~music.f90->sourcefile~tape_recorder_class.f90 sourcefile~envelopes.f90->sourcefile~forsynth.f90 sourcefile~envelopes.f90->sourcefile~tape_recorder_class.f90 sourcefile~signals.f90->sourcefile~envelopes.f90 sourcefile~signals.f90->sourcefile~forsynth.f90 sourcefile~signals.f90->sourcefile~tape_recorder_class.f90 sourcefile~tape_recorder_class.f90->sourcefile~forsynth.f90

Files dependent on this one

sourcefile~~music.f90~~AfferentGraph sourcefile~music.f90 music.f90 sourcefile~all_signals.f90 all_signals.f90 sourcefile~all_signals.f90->sourcefile~music.f90 sourcefile~arpeggios.f90 arpeggios.f90 sourcefile~arpeggios.f90->sourcefile~music.f90 sourcefile~blues.f90 blues.f90 sourcefile~blues.f90->sourcefile~music.f90 sourcefile~chords_and_melody.f90 chords_and_melody.f90 sourcefile~chords_and_melody.f90->sourcefile~music.f90 sourcefile~demo_effects.f90 demo_effects.f90 sourcefile~demo_effects.f90->sourcefile~music.f90 sourcefile~drone_music.f90 drone_music.f90 sourcefile~drone_music.f90->sourcefile~music.f90 sourcefile~misc_sounds.f90 misc_sounds.f90 sourcefile~misc_sounds.f90->sourcefile~music.f90

Source Code

! Forsynth: a multitracks stereo sound synthesis project
! License GPL-3.0-or-later
! Vincent Magnin
! Last modifications: 2024-05-30

!---------------------------------------------------------------------------
!> Contains music theory elements: scales, circle of fifths, chords, etc.
!---------------------------------------------------------------------------
module music
    use forsynth, only: wp
    use signals, only: add_sine_wave, add_karplus_strong
    ! Music theory elements common to the ForMIDI and ForSynth projects:
    use music_common
    use tape_recorder_class
    use envelopes, only: ADSR_envelope

    implicit none
    public

    !> Equal temperament: https://en.wikipedia.org/wiki/Equal_temperament
    real(wp), parameter :: SEMITONE = 2.0_wp**(1.0_wp/12.0_wp)
    !> Concert pitch (A note):
    real(wp), parameter :: PITCH = 440.0_wp

    public :: SEMITONE, PITCH, add_note, add_chord, fr

contains

    !> A note of fundamental frequency f with harmonics, based on sine waves.
    !> https://en.wikipedia.org/wiki/Harmonic
    subroutine add_note(tape, track, t1, t2, f, Amp, envelope)
        type(tape_recorder), intent(inout) :: tape
        integer, intent(in)  :: track
        real(wp), intent(in) :: t1, t2, f, Amp
        type(ADSR_envelope), optional, intent(inout) :: envelope
        integer :: h

        ! Adding harmonics 1f to 40f, with a decreasing amplitude:
        do h = 1, 40
            call add_sine_wave(tape, track, t1, t2, h*f, Amp / h**2, envelope)
        end do
    end subroutine

    !> Writes a chord using an array containing the intervals
    !> (see the music_common module)
    subroutine add_chord(tape, track, t1, t2, f, Amp, chord, envelope)
        type(tape_recorder), intent(inout) :: tape
        integer, intent(in)  :: track
        real(wp), intent(in) :: t1, t2, f, Amp
        integer, dimension(:), intent(in) :: chord
        type(ADSR_envelope), optional, intent(inout) :: envelope
        integer :: i, interval

        do i = 1, size(chord)
            interval = chord(i)
            call add_note(tape, track, t1, t2, f * SEMITONE**interval, Amp, envelope)
        end do
    end subroutine add_chord

    !> Writes a broken chord using an array containing the intervals
    !> (see the music_common module). It uses plucked strings (Karplus-Strong).
    !> For the moment, each note has the same duration.
    !> https://en.wikipedia.org/wiki/Arpeggio
    subroutine add_broken_chord(tape, track, t1, t2, f, Amp, chord)
        type(tape_recorder), intent(inout) :: tape
        integer, intent(in)  :: track
        real(wp), intent(in) :: t1, t2, f, Amp
        integer, dimension(:), intent(in) :: chord
        integer :: i, interval
        real(wp) :: dnote   ! duration of each note of the chord
        real(wp) :: fnote
        real(wp) :: t

        dnote = (t2-t1) / size(chord)

        t = t1
        do i = 1, size(chord)
            interval = chord(i)
            fnote = f * SEMITONE**interval
            call add_karplus_strong(tape, track, t1=t, t2=t+dnote, f=fnote, Amp=Amp)
            t = t + dnote
        end do
    end subroutine add_broken_chord

    !> Returns the frequency of the note.
    !> The note name is composed of two or three characters,
    !> for example "A4", "A#4", "Ab4", where the final character is
    !> the octave.
    real(wp) function fr(note)
        character(*), intent(in) :: note
        ! 0 <= octave <=9
        integer :: octave
        ! Gap relative to PITCH, in semitones:
        integer :: gap
        ! ASCII code of the 0 character:
        integer, parameter :: zero = iachar('0')

        select case (note(1:1))
            case ('C')
                gap = -9
            case ('D')
                gap = -7
            case ('E')
                gap = -5
            case ('F')
                gap = -4
            case ('G')
                gap = -2
            case ('A')
                gap = 0
            case ('B')
                gap = +2
            case default
                print*, "ERROR! Note name unknown..."
                stop
        end select

        ! Treating accidentals (sharp, flat) and computing the octave:
        select case (note(2:2))
            case ('b')
                gap = gap - 1
                octave = iachar(note(3:3)) - zero
            case ('#')
                gap = gap + 1
                octave = iachar(note(3:3)) - zero
            case default
                octave = iachar(note(2:2)) - zero
        end select

        if ((octave >= 0) .and. (octave <= 9)) then
            gap = gap + (octave - 4) * 12
        else
            print *, "ERROR! Octave out of bounds [0; 9]"
            stop
        end if

        ! Computing the frequency of the note:
        fr = PITCH * SEMITONE**(real(gap, wp))
    end function fr
end module music