wav_file_class.f90 Source File


This file depends on

sourcefile~~wav_file_class.f90~~EfferentGraph sourcefile~wav_file_class.f90 wav_file_class.f90 sourcefile~forsynth.f90 forsynth.f90 sourcefile~wav_file_class.f90->sourcefile~forsynth.f90 sourcefile~tape_recorder_class.f90 tape_recorder_class.f90 sourcefile~wav_file_class.f90->sourcefile~tape_recorder_class.f90 sourcefile~tape_recorder_class.f90->sourcefile~forsynth.f90

Files dependent on this one

sourcefile~~wav_file_class.f90~~AfferentGraph sourcefile~wav_file_class.f90 wav_file_class.f90 sourcefile~all_signals.f90 all_signals.f90 sourcefile~all_signals.f90->sourcefile~wav_file_class.f90 sourcefile~arpeggios.f90 arpeggios.f90 sourcefile~arpeggios.f90->sourcefile~wav_file_class.f90 sourcefile~blues.f90 blues.f90 sourcefile~blues.f90->sourcefile~wav_file_class.f90 sourcefile~chords_and_melody.f90 chords_and_melody.f90 sourcefile~chords_and_melody.f90->sourcefile~wav_file_class.f90 sourcefile~demo_effects.f90 demo_effects.f90 sourcefile~demo_effects.f90->sourcefile~wav_file_class.f90 sourcefile~doppler_effect.f90 doppler_effect.f90 sourcefile~doppler_effect.f90->sourcefile~wav_file_class.f90 sourcefile~drone_music.f90 drone_music.f90 sourcefile~drone_music.f90->sourcefile~wav_file_class.f90 sourcefile~drum_machine.f90 drum_machine.f90 sourcefile~drum_machine.f90->sourcefile~wav_file_class.f90 sourcefile~misc_sounds.f90 misc_sounds.f90 sourcefile~misc_sounds.f90->sourcefile~wav_file_class.f90 sourcefile~shepard_risset_glissando.f90 shepard_risset_glissando.f90 sourcefile~shepard_risset_glissando.f90->sourcefile~wav_file_class.f90 sourcefile~shepard_scale.f90 shepard_scale.f90 sourcefile~shepard_scale.f90->sourcefile~wav_file_class.f90

Source Code

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

!> The main class that you will use to create your WAV files.
module wav_file_class
    use, intrinsic :: iso_fortran_env, only: INT16, INT32, INT64
    use forsynth, only: wp, RATE
    use tape_recorder_class

    implicit none
    !> Maximum amplitude in a WAV [-32768 ; +32767]:
    integer, parameter :: MAX_AMPLITUDE = 32767
    integer :: status

    type, extends(tape_recorder)    :: WAV_file
      character(len=:), allocatable :: filename
      integer                       :: fileunit
    contains
      procedure :: create_WAV_file
      procedure :: close_WAV_file
      procedure :: get_name
      procedure, private :: write_header
      procedure, private :: write_normalized_data
    end type WAV_file

    private :: status

    public :: WAV_file

contains

    !> Create a WAV file with a header:
    subroutine create_WAV_file(self, filename, tracks, duration)
        class(WAV_file), intent(inout) :: self
        character(*), intent(in)       :: filename
        integer, intent(in)  :: tracks
        real(wp), intent(in) :: duration

        call self%new(tracks, duration)

        self%filename   = filename
        open(newunit=self%fileunit, file=self%filename, access='stream', status='replace', action='write')
        call self%write_header()
    end subroutine create_WAV_file

    !> Returns the name of the WAV file:
    function get_name(self)
        class(WAV_file), intent(inout) :: self
        character(len(self%filename)) :: get_name

        get_name = self%filename
    end function

    !> Creates the 44 bytes WAV header and prints some information:
    subroutine write_header(self)
        class(WAV_file), intent(inout)  :: self
        !****************
        ! WAV parameters:
        !****************
        ! Number of channels: 1 for mono, 2 for stereo, etc.
        integer(INT16), parameter :: CHANNELS = 2
        integer(INT16), parameter :: BITS_PER_SAMPLE = 16
        integer(INT64) :: DATA_BYTES
        integer(INT32) :: file_size, bytes_per_second, data_size
        integer(INT16) :: bytes_per_sample

        print *, "Nb of tracks, excluding track 0:", self%tracks

        DATA_BYTES = (BITS_PER_SAMPLE / 8) * CHANNELS * self%samples
        print *, "Used RAM:   ", DATA_BYTES * self%tracks, "bytes"
        print *, "File size ~ ", DATA_BYTES, "bytes"

        associate(u => self%fileunit)
            ! RIFF format:
            write(u, iostat=status) "RIFF"
            ! Remaining bytes after this data:
            file_size = 36 + DATA_BYTES
            write(u, iostat=status) file_size

            write(u, iostat=status) "WAVE"

            ! ***** First sub-chunk *****
            ! Don't remove the final space in the string!
            write(u, iostat=status) "fmt "
            ! Remaining bytes in this sub-chunk, 16 for PCM (32 bits integer):
            write(u, iostat=status) 16_INT32
            ! Encoding is 1 for PCM (16 bits integer):
            write(u, iostat=status) 1_INT16

            write(u, iostat=status) int(CHANNELS, kind=INT16)
            ! Sampling frequency:
            write(u, iostat=status) int(RATE, kind=INT32)

            bytes_per_second = RATE * CHANNELS * (BITS_PER_SAMPLE / 8)
            write(u, iostat=status) bytes_per_second

            bytes_per_sample = CHANNELS * (BITS_PER_SAMPLE / 8)
            write(u, iostat=status) bytes_per_sample

            write(u, iostat=status) BITS_PER_SAMPLE

            ! ***** Second sub-chunk *****
            write(u, iostat=status) "data"

            data_size = self%samples * CHANNELS * (BITS_PER_SAMPLE / 8)
            write(u, iostat=status) data_size
        end associate
    end subroutine write_header

    !> This method normalizes the sound amplitude on track 0, before saving
    !> the left and right channels in the WAV file.
    subroutine write_normalized_data(self)
        class(WAV_file), intent(inout)  :: self
        integer  :: i
        real(wp) :: maxi

        ! Looking for the maximum amplitude (must not be zero):
        maxi = max(1e-16_wp, maxval(abs(self%left(0, :))), maxval(abs(self%right(0, :))))

        do i = 0 , self%samples
            ! Writing the amplitude of left then right channels as 16 bit signed integers:
            write(self%fileunit, iostat=status) nint((self%left(0, i)  / maxi * MAX_AMPLITUDE), kind=INT16)
            write(self%fileunit, iostat=status) nint((self%right(0, i) / maxi * MAX_AMPLITUDE), kind=INT16)
        end do
    end subroutine

    !> Must be called at the end. It normalizes the channels, writes them in the
    !> WAV file and closes it. It also deallocate the tape arrays.
    subroutine close_WAV_file(self)
        class(WAV_file), intent(inout)  :: self

        call self%write_normalized_data()
        close(self%fileunit, iostat=status)

        call self%tape_recorder%finalize()
    end subroutine

end module wav_file_class