Audio frequency generator

From Rosetta Code
Audio frequency generator is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

An audio frequency generator produces a continual audible monotone at a set frequency and level of volume. There are controls to adjust the frequency and the volume up and down as desired. Some also have a selector to switch the waveform type between sine wave, square wave and triangular sawtooth.

The task is to emulate an audio frequency generator. It is permissible to use an inbuilt computer speaker if the system does not have the facility to utilize dedicated sound hardware.

The solution should include:

  • A demonstration of how to check for availability of sound hardware on the system (on systems where this is possible)
  • A demonstration of how to produce a continual audible monotone (on sound hardware this would typically be a sine wave)
  • A method of adjusting the frequency and volume of the monotone. (one way would be to use left and right arrow keys to increase or decrease frequency, and up and down keys to increase or decrease the volume)
  • A method to silence or reset the sound hardware on exit.

Optionally the solution can also include:

  • A demonstration of how to fall back to internal speaker, if sound hardware is not available
  • A facility to switch between sine wave, square wave and triangular sawtooth

Languages that provide no facilities for utilizing sound hardware of any kind should be omitted.

Action!

byte
  volu,dist,freq,key=764

proc INFO()
 position(5,11)
 printf("volume:%B freq:%B distortion:%B  ",volu,freq,dist)
 sound(0,freq,dist,volu)
 key=255
return

proc INI()
 freq=$46
 volu=10
 dist=10
 INFO()
return

proc GENERATOR()
 byte dmactls=559,cur=752,mar=82
 card dlist=560

 graphics(0) cur=1 mar=8
 POKE(709,0) POKE(710,14)
 POKEC(DLIST+8,0)
 POKEC(DLIST+10,0)
 POKEC(DLIST+19,0)
 POKEC(DLIST+21,0)
 POKEC(DLIST+26,0)
 POKE (DLIST+28,0)

 position(8,1)
 printe("Action! sound generator")
 printe("")
 printe("")
 printe("left/right - set volume")
 printe("up/down    - freq +/-")
 printe("space      - distorion")
 printe("return     - default (440 Hz)")
 printe("esc        - exit")

 INI()

  do 
   if key=28 then exit fi
   if key=12 then INI() fi
   if key=14 then freq==-1 INFO() fi
   if key=15 then freq==+1 INFO() fi
   if key=7 then volu==+1 if volu>14 then volu=15 fi INFO() fi
   if key=6 then volu==-1 if volu=255 then volu=0 fi INFO() fi
   if key=33 then dist==+2 if dist>15 then dist=0 fi INFO() fi
  od

 sndrst() mar=2 graphics(0)
return

Axe

This example is untested. Please check that it's correct, debug it as necessary, and remove this message.


ClrHome
Disp "FREQ:",i
10→F
Repeat getKey(15)
 If getKey(3)
  F++
  F=0?-1→F
 ElseIf getKey(2)
  F--
  F=-1?0→F
 End
 Output(5,0,F▶Dec)
 Freq(F,10000)
End

FreeBASIC

Library: BASS

#include "bass.bi"
#include "fbgfx.bi"

Dim Shared As Integer freq = 440, volume = 50
Dim Shared As HSTREAM stream

Sub CheckKeys()
    If Multikey(SC_LEFT)  Then freq -= 10
    If Multikey(SC_RIGHT) Then freq += 10
    If Multikey(SC_UP)    Then volume += 5
    If Multikey(SC_DOWN)  Then volume -= 5
    
    ' Limit frequency and volume to reasonable values
    If freq < 20 Then freq = 20
    If freq > 20000 Then freq = 20000
    If volume < 0 Then volume = 0
    If volume > 100 Then volume = 100
    
    ' Update the stream with the new frequency and volume
    BASS_ChannelSetAttribute(stream, BASS_ATTRIB_FREQ, freq)
    BASS_ChannelSetAttribute(stream, BASS_ATTRIB_VOL, volume / 100.0)
End Sub

' Initialize BASS using the default device at 44.1 KHz.
If (BASS_Init(-1, 44100, 0, 0, 0) = False) Then
	Print "Could not initialize audio! BASS returned error " & BASS_ErrorGetCode()
	Sleep
	End
End If

' Create a stream
stream = BASS_StreamCreate(44100, 1, 0, @BASS_STREAMPROC_PUSH, NULL)
If stream = 0 Then
    Print "Error creating stream: "; BASS_ErrorGetCode()
    BASS_Free
    Sleep
    End 1
End If

' Start the stream
BASS_ChannelPlay(stream, False)

Do
    CheckKeys
    Sleep 10
Loop Until Inkey$ <> ""

' Clean up
BASS_StreamFree(stream)
BASS_Free

FutureBasic

Using the free FutureBasic IDE, this code compiles as a stand-alone Macintosh Audio Frequency Generator.

include "Tlbx AVKit.incl"

_window = 1
begin enum 1
  _freqLabel
  _freqSlider
  _freqFld
  _hzLabel
  _ampLabel
  _ampSlider
  _ampFld
  _infoFld
  _playBtn
end enum

void local fn FixViews
  
  select ( fn ButtonState( _playBtn ) )
    case NSControlStateValueOn
      slider    _freqSlider, YES
      textfield _freqFld,    YES
      slider    _ampSlider,  YES
      textfield _ampFld,     YES
    case NSControlStateValueOff
      slider    _freqSlider, NO
      textfield _freqFld,    NO
      slider    _ampSlider,  NO
      textfield _ampFld,     NO
  end select
end fn

void local fn BuildWindow
  window _window, @“Audio Frequency Generator”, (0,0,455,193), NSWindowStyleMaskTitled + NSWindowStyleMaskClosable + NSWindowStyleMaskMiniaturizable
  
  textlabel _freqLabel, @"Frequency:", (18,158,63,14)
  ControlSetSize( _freqLabel, NSControlSizeSmall )
  
  slider _freqSlider,, 441, (87,151,266,22), 20, 7400,, _window
  ControlSetSize( _freqSlider, NSControlSizeSmall )
  ControlSetContinuous( _freqSlider, YES )
  
  textfield _freqFld,, @"1000.0", (361,156,55,19)
  ControlSetSize( _freqFld, NSControlSizeSmall )
  ControlSetAlignment( _freqFld, NSTextAlignmentCenter )
  ControlSetFormat( _freqFld, @"0123456789.", YES, 0, 7 )
  
  textlabel _hzLabel, @"Hz", (418,158,19,14)
  ControlSetSize( _hzLabel, NSControlSizeSmall )
  
  textlabel _ampLabel, @"Amplitude:", (18,106,63,14)
  ControlSetSize( _ampLabel, NSControlSizeSmall )
  
  slider _ampSlider,, 0.2, (87,100,266,22), 0, 1 , 11
  ControlSetSize( _ampSlider, NSControlSizeSmall )
  ControlSetContinuous( _ampSlider, YES )
  
  textfield _ampFld,, @"0.20", (361,104,55,19)
  ControlSetSize( _ampFld, NSControlSizeSmall )
  ControlSetAlignment( _ampFld, NSTextAlignmentCenter )
  ControlSetFormat( _ampFld, @"0123456789.", YES, 0, 7 )
  
  textlabel _infoFld,, (20,15,400,76), _window
  ControlSetAlignment( _infoFld, NSTextAlignmentLeft )
  
  button _playBtn,YES,NSControlStateValueOff, @"Play tone", (345,18,91,24), NSButtonTypeMomentaryLight, NSBezelStyleRegularSquare, _window
  
  fn FixViews
end fn

void local fn PlayFrequency( frequency as float, amplitude as float )
  AVAudioEngineRef audioEngine = fn AVAudioEngineInit
  AppSetProperty( @"engine", audioEngine )
  
  AVAudioPlayerNodeRef player = fn AVAudioPlayerNodeInit
  AppSetProperty( @"player", player )
  
  AVAudioMixerNodeRef mixer = fn AVAudioEngineMainMixerNode( audioEngine )
  float sampleRate = fn AVAudioFormatSampleRate( fn AVAudioNodeOutputFormatForBus( mixer, 0 ) )
  AVAudioFrameCount frameBufferLength = fn floorf(sampleRate / frequency) * 1
  AVAudioPCMBufferRef buffer = fn AVAudioPCMBufferWithFormat( fn AVAudioNodeOutputFormatForBus( player, 0 ), frameBufferLength )
  AVAudioPCMBufferSetFrameLength( buffer, frameBufferLength )
  NSInteger channelCount = fn AVAudioFormatChannelCount( fn AVAudioNodeOutputFormatForBus( mixer, 0 ) )
  AVAudioFrameCount frameLength = fn AVAudioPCMBufferFrameLength(buffer)
  CFStringRef infoStr = fn StringWithFormat( @"%ld channels\nfrequency = %.1f Hz        amplitude = %.2f\nsample rate = %.1f         frame length = %u", channelCount, frequency, amplitude, sampleRate, frameLength )
  ControlSetStringValue( _infoFld, infoStr )
  
  ptr p = (ptr)fn AVAudioPCMBufferFloatChannelData(buffer)
  xref floatChannelData(100) as ^float
  floatChannelData = p
  
  long i, channelNumber
  float theta, value
  ^float channelBuffer
  
  for i = 0 to frameLength - 1
    theta = frequency * i * 2.0 * M_PI / sampleRate
    value = fn sinf(theta)
    for channelNumber = 0 to channelCount - 1
      channelBuffer = floatChannelData(channelNumber)
      cln channelBuffer[i] = value * amplitude;
    next
  next
  
  AVAudioEngineAttachNode( audioEngine, player )
  AVAudioEngineConnect( audioEngine, player, mixer, fn AVAudioNodeOutputFormatForBus( player, 0 ) )
  fn AVAudioEngineStart( audioEngine, NULL )
  AVAudioPlayerNodePlay( player )
  AVAudioPlayerNodeScheduleBufferAtTime( player, buffer, NULL, AVAudioPlayerNodeBufferLoops, NULL, NULL )
end fn

void local fn PlayStopAction
  AVAudioPlayerNodeRef player
  select ( fn ButtonState( _playBtn ) )
    case NSControlStateValueOn
      ButtonSetTitle( _playBtn, @"Stop tone" )
      fn PlayFrequency( fn ControlDoubleValue(_freqFld), fn ControlDoubleValue(_ampFld) )
    case NSControlStateValueOff
      player = fn AppProperty( @"player" )
      AVAudioPlayerNodeStop( player )
      AppRemoveAllProperties
      ButtonSetTitle( _playBtn, @"Play tone" )
  end select
  fn FixViews
end fn

local fn StopPlayer
  AVAudioPlayerNodeRef player
  player = fn AppProperty( @"player" )
  AVAudioPlayerNodeStop( player )
  AppRemoveAllProperties
end fn

local fn AdjustFrequency
  fn StopPlayer
  double frequency = fn ControlDoubleValue(_freqSlider)
  CFStringRef freqStr = fn StringWithFormat( @"%.1f", frequency )
  ControlSetStringValue(_freqFld, freqStr )
  fn PlayFrequency( fn ControlDoubleValue(_freqFld), fn ControlDoubleValue(_ampFld) )
end fn

local fn AdjustAmplitude
  fn StopPlayer
  double amplitude = fn ControlDoubleValue(_ampSlider)
  CFStringRef ampStr = fn StringWithFormat( @"%.2f", amplitude )
  ControlSetStringValue(_ampFld, ampStr )
  fn PlayFrequency( fn ControlDoubleValue(_freqFld), fn ControlDoubleValue(_ampFld) )
end fn

local fn UserFrequency
  double frequency = fn ControlDoubleValue(_freqFld)
  
  fn StopPlayer
  if ( ( frequency < 20.0 ) or ( frequency > 7400.0 ) )
    alert 1, NSAlertStyleWarning, @"Frequency must be between 20.0 Hz and 7400.0 Hz.", @"Please enter value in range.", @"Okay", YES
  else
    CFStringRef freqStr = fn StringWithFormat( @"%.1f", frequency )
    ControlSetStringValue(_freqFld, freqStr )
    ControlSetDoubleValue( _freqSlider, frequency )
    fn PlayFrequency( fn ControlDoubleValue(_freqFld), fn ControlDoubleValue(_ampFld) )
  end if
end fn

local fn UserAmplitude
  double amplitude = fn ControlDoubleValue(_ampFld)
  
  fn StopPlayer
  if ( amplitude > 1.0 )
    alert 1, NSAlertStyleWarning, @"Amplitude (volume) must be between 0.0 (silent) and 1.0 (max volume).", @"Please enter value in range.", @"Okay", YES
  else
    ControlSetDoubleValue(_ampSlider, amplitude)
    CFStringRef ampStr = fn StringWithFormat( @"%.2f", amplitude )
    ControlSetStringValue(_ampFld, ampStr )
    fn PlayFrequency( fn ControlDoubleValue(_freqFld), fn ControlDoubleValue(_ampFld) )
  end if
end fn

void local fn DoDialog( ev as long, tag as long )
  select ( ev )
    case _btnClick
      select ( tag )
        case _freqSlider  : fn AdjustFrequency
        case _ampSlider   : fn AdjustAmplitude
        case _playBtn     : fn PlayStopAction
      end select
    case _controlTextDidEndEditing
      select ( tag )
        case _freqFld     : fn UserFrequency
        case _ampFld      : fn UserAmplitude
      end select
    case _windowWillClose : end
  end select
end fn

fn BuildWindow

on dialog fn DoDialog

HandleEvents


Go

As Go does not have any audio support in its standard library, this invokes the SoX utility's 'play' command with the appropriate parameters to emulate an audio frequency generator. It appears that SoX automatically uses the internal speaker if there is no sound hardware available.

The duration of the monotone is set in advance (to a small number of seconds) and the application ends when it finishes playing. Consequently, a method to silence it is not required.

package main

import (
    "bufio"
    "fmt"
    "log"
    "os"
    "os/exec"
    "strconv"
)

func check(err error) {
    if err != nil {
        log.Fatal(err)
    }
}

func main() {
    scanner := bufio.NewScanner(os.Stdin)
    freq := 0
    for freq < 40 || freq > 10000 {
        fmt.Print("Enter frequency in Hz (40 to 10000) : ")
        scanner.Scan()
        input := scanner.Text()
        check(scanner.Err())
        freq, _ = strconv.Atoi(input)
    }
    freqS := strconv.Itoa(freq)

    vol := 0
    for vol < 1 || vol > 50 {
        fmt.Print("Enter volume in dB (1 to 50) : ")
        scanner.Scan()
        input := scanner.Text()
        check(scanner.Err())
        vol, _ = strconv.Atoi(input)
    }
    volS := strconv.Itoa(vol)

    dur := 0.0
    for dur < 2 || dur > 10 {
        fmt.Print("Enter duration in seconds (2 to 10) : ")
        scanner.Scan()
        input := scanner.Text()
        check(scanner.Err())
        dur, _ = strconv.ParseFloat(input, 64)
    }
    durS := strconv.FormatFloat(dur, 'f', -1, 64)

    kind := 0
    for kind < 1 || kind > 3 {
        fmt.Print("Enter kind (1 = Sine, 2 = Square, 3 = Sawtooth) : ")
        scanner.Scan()
        input := scanner.Text()
        check(scanner.Err())
        kind, _ = strconv.Atoi(input)
    }
    kindS := "sine"
    if kind == 2 {
        kindS = "square"
    } else if kind == 3 {
        kindS = "sawtooth"
    }

    args := []string{"-n", "synth", durS, kindS, freqS, "vol", volS, "dB"}
    cmd := exec.Command("play", args...)
    err := cmd.Run()
    check(err)
}


Julia

Uses the PortAudio library.

using PortAudio

if Sys.iswindows()
    getch() = @threadcall((:_getch, "msvcr100.dll"), Cint, ())
else
    getch() = @threadcall((:getch, libcurses), Cint, ())
end

function audiodevices()
    println(rpad("Index", 6), rpad("Device Name", 44), rpad("API", 16), rpad("In", 4),
        rpad("Out", 4), rpad("Sample Rate", 8))
    for (i, dev) in enumerate(PortAudio.devices())
        println(rpad(i - 1, 6), rpad(dev.name, 44), rpad(dev.hostapi, 16),
            rpad(dev.maxinchans, 4), rpad(dev.maxoutchans, 4),
            rpad(dev.defaultsamplerate, 8))
    end
end

println("Listing available hardware:")
audiodevices()

function paudio()
    devs = PortAudio.devices()
    devnum = findfirst(x -> x.maxoutchans > 0, devs)
    (devnum == nothing) && error("No output device for audio found")
    println("Enter a device # from the above, or Enter for default: ")
    n = tryparse(Int, strip(readline()))
    devnum = n == nothing ? devnum : n + 1
    return PortAudioStream(devs[devnum].name, 0, 2)
end

play(ostream, sample::Array{Float64,1}) = write(ostream, sample)
play(ostr, sample::Array{Int64,1}) = play(ostr, Float64.(sample))

struct Note{S<:Real, T<:Real}
    pitch::S
    duration::T
    volume::T
    sustained::Bool
end

sinewave(t) = 0.6sin(t) + 0.2sin(2t) + .05*sin(8t)
squarewave(t) = iseven(Int(trunc(t / π))) ? 1.0 : -1.0
sawtoothwave(t) = rem(t, 2π)/π - 1

function play(ostream, A::Note, samplingfreq::Real=44100, shape::Function=sinewave, pause=true)
    timesamples = 0:(1 / samplingfreq):(A.duration * (A.sustained ? 0.98 : 0.9))
    v = Float64[shape(2π * A.pitch * t) for t in timesamples]
    if !A.sustained
        decay_length = div(length(timesamples), 5)
        v[end-decay_length:end-1] = v[end-decay_length:end-1] .* LinRange(1, 0, decay_length)
    end
    v .*= A.volume
    play(ostream, v)
    if pause
        sleep(A.duration)
    end
end

function inputtask(channel)
    println("""
    \nAllow several seconds for settings changes to take effect.
    Arrow keys:
    Volume up: up Volume down: down Frequency up: right Frequency down: left
    Sine wave(default): s Square wave: a  Sawtooth wave: w
    To exit: x
    """)
    while true
        inputch = Char(getch())
        if inputch == 'à'
            inputch = Char(getch())
        end
        put!(channel, inputch)
        sleep(0.2)
    end
end

function playtone(ostream)
    volume = 0.5
    pitch = 440.0
    waveform = sinewave
    while true
        if isready(chan)
            ch = take!(chan)
            if ch == 'H'
                volume = min(volume * 2.0, 1.0)
            elseif ch == 'P'
                volume = max(volume * 0.5, 0.0)
            elseif ch == 'M'
                pitch = min(pitch * 9/8, 20000)
            elseif ch == 'K'
                pitch = max(pitch * 7/8, 32)
            elseif ch == 's'
                waveform = sinewave
            elseif ch == 'a'
                waveform = squarewave
            elseif ch == 'w'
                waveform = sawtoothwave
            elseif ch == 'x'
                break
            end
        end
        play(ostream, Note(pitch, 4.5, volume, true), 44100.0, waveform, false)
    end
    exit()
end

const ostr = paudio()
const chan = Channel{Char}(1)
const params = Any[]

@async(inputtask(chan))
playtone(ostr)

Locomotive Basic

10 mode 1:input "Enter initial frequency in Hz";f:cls
20 if sq(2)<128 then sound 2,62500/f,100
30 a$=inkey$
40 if a$="h" then f=f+10
50 if a$="l" then f=f-10
60 if a$="q" then end
70 locate 1,1:print f"Hz   "
80 print:print " Use h and l to adjust frequency;":print "  q to quit."
90 goto 20

Nim

Translation of: Go

As in the Go version, we use the Sox "play" command to emulate the audio frequency generator. This version is a faithful translation of the Go version, even if the way things are done is pretty different.

import osproc, strutils

type Waveform {.pure.} = enum
  Sine = (1, "sine")
  Square = (2, "square")
  Sawtooth = (3, "sawtooth")

proc getIntValue(msg: string; minval, maxval: int): int =
  while true:
    stdout.write msg
    stdout.flushFile()
    try:
      result = stdin.readLine.strip().parseInt()
      if result notin minval..maxval:
        echo "Invalid value"
      else:
        return
    except ValueError:
      echo "Error: invalid value."
    except EOFError:
      echo()
      quit "Quitting.", QuitFailure

let freq = getIntValue("Enter frequency in Hz (40 to 10000): ", 40, 10_000)
let vol = getIntValue("Enter volume in dB (1 to 50): ", 1, 50)
let dur = getIntValue("Enter duration in seconds (2 to 10): ", 2, 10)
let kind = Waveform getIntValue("Enter kind (1 = sine, 2 = square, 3 = sawtooth): ", 1, 3)

let args = ["-n", "synth", $dur, $kind, $freq, "vol", $vol, "dB"]
echo execProcess("play", args = args, options = {poStdErrToStdOut, poUsePath})

Perl

use strict 'vars';
use feature 'say';
use feature 'state';
use Audio::NoiseGen qw(play sine square triangle);
use Term::ReadKey qw(ReadMode ReadLine);

Audio::NoiseGen::init() || die 'No access to sound hardware?';

print "Play [S]ine, s[Q]uare or [T]riangle wave? "; my $ans_freq   = uc(<>);
print "Pick a volume [0-9]";                        my $ans_volume =    <>;
say 'Volume: '. (my $volume = 0.1 + 1 * $ans_volume/10);

ReadMode(3);

my $waveform = $ans_freq eq 'Q' ? 'square' : $ans_freq eq 'T' ? 'triangle' : 'sine';
play ( gen => amp ( amount => $volume, gen => &$waveform( freq => setfreq(440) ) ) );

sub setfreq {
    state $freq;
    say $freq = shift;
    return sub {
        ReadMode(3);
        state $cnt;
        unless ($cnt++ % 1000) {
            my $key = ReadLine(-1);
            my $previous = $freq;
            if    ($key eq "\e[A") { $freq += 10  }
            elsif ($key eq "\e[B") { $freq -= 10  }
            elsif ($key eq "\e[C") { $freq += 100 }
            elsif ($key eq "\e[D") { $freq -= 100 }
            say $freq if $freq != $previous;
        }
        return $freq;
    }
}

Phix

-- demo/rosetta/Audio_frequency_generator.exw
include pGUI.e
Ihandle dlg, frequency, duration
 
atom k32=0, xBeep
 
function button_cb(Ihandle /*playbtn*/)
    integer f = IupGetInt(frequency,"VALUE"),
            d = IupGetInt(duration,"VALUE")
    if platform()=WINDOWS then
        if k32=0 then
            k32 = open_dll("kernel32.dll")
            xBeep = define_c_proc(k32, "Beep", {C_INT,C_INT})
        end if
        c_proc(xBeep,{f,d})
    else
        system(sprintf("play -n synth %f sine %d", {d/1000, f}))
    end if
    end if
    return IUP_DEFAULT
end function
 
function valuechanged_cb(Ihandle val)
    -- maintain the labels as the sliders are moved
    Ihandle parent = IupGetParent(val),
            lbl = IupGetNextChild(parent, NULL) 
    integer v = IupGetInt(val,"VALUE")
    IupSetInt(lbl,"TITLE",v)
    return IUP_DEFAULT
end function
 
procedure main()
    Ihandle flabel, dlabel, frame1, frame2, playbtn
 
    IupOpen()
 
    flabel = IupLabel("2000","ALIGNMENT=ARIGHT,NAME=val_label,SIZE=20x8")
    frequency = IupValuator("HORIZONTAL","VALUECHANGED_CB", Icallback("valuechanged_cb"),
                            "EXPAND=HORIZONTAL, CANFOCUS=NO, MIN=50, MAX=10000, VALUE=2000")
    frame1 = IupFrame(IupHbox({flabel,frequency}),"TITLE=\"Frequency (Hz): \"")
 
    dlabel = IupLabel("500","ALIGNMENT=ARIGHT,NAME=val_label,SIZE=20x8")
    duration = IupValuator("HORIZONTAL","VALUECHANGED_CB", Icallback("valuechanged_cb"),
                           "EXPAND=HORIZONTAL, CANFOCUS=NO, MIN=100, MAX=3000, VALUE=500")
    frame2 = IupFrame(IupHbox({dlabel,duration}),"TITLE=\"Duration (ms): \"")
 
    playbtn = IupHbox({IupFill(),
                       IupButton("Play",Icallback("button_cb"),"PADDING=30x0"),
                       IupFill()},"MARGIN=0x20")
 
    dlg = IupDialog(IupVbox({frame1,
                             frame2,
                             playbtn}, "MARGIN=10x5, GAP=5"))
    IupSetAttribute(dlg,"TITLE","Audio Frequency Generator")
    IupSetAttribute(dlg,"RASTERSIZE","500x230")
 
    IupShow(dlg)
    IupMainLoop()
    IupClose()
end procedure
main()

Pure Data

audio-frequency-generator.pd

#N canvas 245 70 635 516 10;
#X obj 501 214 cnv 15 84 66 empty empty empty 80 12 0 10 -262130 -13381 0;
#X obj 319 98 cnv 15 133 15 empty empty frequency 74 8 0 10 -204786 -13381 0;
#X obj 319 55 line;
#X floatatom 319 98 8 0 0 1 - - -;
#X obj 109 436 dac~;
#X obj 322 261 hsl 128 15 0 1 0 0 empty empty volume 90 8 0 10 -203904 -1 -4160 0 0;
#X obj 85 142 osc~;
#X floatatom 554 34 5 0 0 0 MIDI_note_number - -;
#X obj 554 52 mtof;
#X obj 32 142 phasor~;
#X obj 119 371 *~;
#X obj 227 345 line~;
#X floatatom 319 285 5 0 0 0 - - -;
#X floatatom 154 444 5 0 0 1 dB - -;
#X obj 154 423 env~ 256;
#X obj 154 462 - 100;
#X obj 320 357 vu 15 120 empty empty -1 -8 0 10 -66577 -1 1 0;
#X msg 227 323 \$1 20;
#X obj 32 292 *~;
#X obj 84 293 *~;
#X obj 120 293 *~;
#X obj 319 142 tgl 15 0 empty empty sawtooth 20 7 0 10 -261234 -1 -258113 0 1;
#X obj 319 165 tgl 15 0 empty empty sine 20 7 0 10 -261234 -1 -258113 0 1;
#X obj 319 188 tgl 15 0 empty empty square 20 7 0 10 -261234 -1 -258113 0 1;
#N canvas 0 0 450 300 (subpatch) 0;
#X array graph 100 float 0;
#X coords 0 1 99 -1 200 140 1 0 0;
#X restore 386 339 graph;
#X obj 63 239 bng 15 250 50 0 empty empty empty 17 7 0 10 -262144 -228856 -1;
#X obj 173 142 triang~;
#X obj 172 293 *~;
#X obj 319 211 tgl 15 0 empty empty triangle 20 7 0 10 -261234 -1 -258113 0 1;
#X obj 120 142 square~;
#X obj 226 142 pulse~;
#X obj 227 293 *~;
#X obj 319 234 tgl 15 0 empty empty pulse 20 7 0 10 -261234 -1 -258113 0 1;
#X obj 32 164 -~ 0.5;
#X obj 45 462 tabwrite~ graph;
#X obj 529 193 loadbang;
#X msg 529 215 \; pd dsp 1;
#X msg 319 34 18 \, 24000 30000;
#X msg 529 250 \; pd dsp 0;
#X text 509 223 on;
#X text 503 258 off;
#X msg 32 34 50;
#X msg 63 34 100;
#X msg 94 34 200;
#X msg 126 34 500;
#X msg 157 34 1000;
#X msg 195 34 2000;
#X msg 232 34 4000;
#X msg 268 34 8000;
#X connect 2 0 3 0;
#X connect 3 0 6 0;
#X connect 3 0 9 0;
#X connect 3 0 25 0;
#X connect 3 0 26 0;
#X connect 3 0 29 0;
#X connect 3 0 30 0;
#X connect 5 0 12 0;
#X connect 5 0 25 0;
#X connect 6 0 19 0;
#X connect 7 0 8 0;
#X connect 8 0 3 0;
#X connect 9 0 33 0;
#X connect 10 0 4 0;
#X connect 10 0 4 1;
#X connect 10 0 14 0;
#X connect 10 0 34 0;
#X connect 11 0 10 1;
#X connect 12 0 17 0;
#X connect 13 0 15 0;
#X connect 14 0 13 0;
#X connect 15 0 16 0;
#X connect 17 0 11 0;
#X connect 18 0 10 0;
#X connect 19 0 10 0;
#X connect 20 0 10 0;
#X connect 21 0 18 1;
#X connect 21 0 25 0;
#X connect 22 0 19 1;
#X connect 22 0 25 0;
#X connect 23 0 20 1;
#X connect 23 0 25 0;
#X connect 25 0 34 0;
#X connect 26 0 27 0;
#X connect 27 0 10 0;
#X connect 28 0 27 1;
#X connect 28 0 25 0;
#X connect 29 0 20 0;
#X connect 30 0 31 0;
#X connect 31 0 10 0;
#X connect 32 0 25 0;
#X connect 32 0 31 1;
#X connect 33 0 18 0;
#X connect 35 0 36 0;
#X connect 37 0 2 0;
#X connect 41 0 3 0;
#X connect 42 0 3 0;
#X connect 43 0 3 0;
#X connect 44 0 3 0;
#X connect 45 0 3 0;
#X connect 46 0 3 0;
#X connect 47 0 3 0;
#X connect 48 0 3 0;

square~.pd

#N canvas 787 211 450 300 10;
#X obj 46 17 inlet;
#X obj 112 86 * -1;
#X obj 46 109 phasor~;
#X obj 46 186 -~ 1;
#X obj 112 109 phasor~;
#X obj 151 17 loadbang;
#X msg 108 59 0;
#X obj 47 223 outlet~;
#X msg 151 59 0.5;
#X obj 46 165 +~;
#X obj 108 17 inlet;
#X connect 0 0 1 0;
#X connect 0 0 2 0;
#X connect 1 0 4 0;
#X connect 2 0 9 0;
#X connect 3 0 7 0;
#X connect 4 0 9 1;
#X connect 5 0 6 0;
#X connect 5 0 8 0;
#X connect 6 0 2 1;
#X connect 8 0 4 1;
#X connect 9 0 3 0;
#X connect 10 0 6 0;
#X connect 10 0 8 0;

triang~.pd

#N canvas 770 214 450 300 10;
#X obj 46 17 inlet;
#X obj 112 51 * -1;
#X obj 46 74 phasor~;
#X obj 46 95 *~ 2;
#X obj 46 116 -~ 1;
#X obj 46 137 clip~ 0 1;
#X obj 112 74 phasor~;
#X obj 112 95 *~ 2;
#X obj 112 116 -~ 1;
#X obj 112 137 clip~ 0 1;
#X obj 194 18 loadbang;
#X msg 194 39 0;
#X obj 47 187 +~;
#X obj 47 208 -~ 0.5;
#X obj 47 229 *~ 2;
#X obj 47 250 outlet~;
#X obj 156 18 inlet;
#X connect 0 0 1 0;
#X connect 0 0 2 0;
#X connect 1 0 6 0;
#X connect 2 0 3 0;
#X connect 3 0 4 0;
#X connect 4 0 5 0;
#X connect 5 0 12 0;
#X connect 6 0 7 0;
#X connect 7 0 8 0;
#X connect 8 0 9 0;
#X connect 9 0 12 1;
#X connect 10 0 11 0;
#X connect 11 0 2 1;
#X connect 11 0 6 1;
#X connect 12 0 13 0;
#X connect 13 0 14 0;
#X connect 14 0 15 0;
#X connect 16 0 11 0;

pulse~.pd

#N canvas 784 384 450 300 10;
#X obj 51 56 phasor~;
#X obj 51 77 -~ 0.99;
#X obj 51 98 clip~ 0 1;
#X obj 51 119 *~ 100;
#X obj 51 140 outlet~;
#X obj 51 34 inlet;
#X obj 90 34 inlet;
#X connect 0 0 1 0;
#X connect 1 0 2 0;
#X connect 2 0 3 0;
#X connect 3 0 4 0;
#X connect 5 0 0 0;
#X connect 6 0 0 1;
  • Provides on/off switch, frequency and volume control and five different wave shapes to select from.
  • Frequency may be typed in or changed by dragging. Predefined pitches and a frequency ramp (18 - 24000 Hz) are accessible by clicking the respective labels.
  • Volume level and wave shape are graphically displayed.
  • More shapes, even free wave shape modelling could easily be added.

Raku

Translation of: Go
# 20240130 Raku programming solution

my ($kindS, $kind, $freq, $vol, $dur) = 'sine', |(0 xx *);

while $freq < 40 || $freq > 10000 {
   print "Enter frequency in Hz (40 to 10000) : ";
   $freq = prompt().Int;
}

while $vol < 1 || $vol > 50 {
   print "Enter volume in dB (1 to 50) : ";
   $vol = prompt().Int;
}

while $dur < 2 || $dur > 10 {
   print "Enter duration in seconds (2 to 10) : ";
   $dur = prompt().Int;
}

while $kind < 1 || $kind > 3 {
   print 'Enter kind (1 = Sine, 2 = Square, 3 = Sawtooth) : ';
   given $kind = prompt().Int {
      when $kind == 2 { $kindS = 'square'   }
      when $kind == 3 { $kindS = 'sawtooth' }
   }
}

my @args = "-n", "synth", $dur, $kindS, $freq, "vol", $vol, "dB";
run 'play', @args, :out('/dev/null'), :err('/dev/null');

Ring

# Project : Audio frequency generator

Load "guilib.ring"
loadlib("C:\Ring\extensions\ringbeep\ringbeep.dll")

freq = 1000
ImageFile  = "stock.jpg"

    UserIcons = CurrentDir() +"\"

    WinLeft   = 80                  
    WinTop    = 80                  
    WinWidth  = 1200                
    WinHeight = 750                
    WinRight  = WinLeft + WinWidth  
    WinBottom = WinTop  + WinHeight 

    BoxLeft   = 80                  
    BoxTop    = 40                  
    BoxWidth  = WinWidth  -160      
    BoxHeight = WinHeight -100      
    imageW = 400 ;  imageH = 400 ; GrowBy = 8
    volume = 100

MyApp = New qapp
{

    win1 = new qMainWindow()
    {
            setwindowtitle("Video and Music Player")
            setgeometry( WinLeft, WinTop, WinWidth, WinHeight)

             if Fexists(ImageFile)

                imageStock = new qlabel(win1)
                {
                    image = new qpixmap(ImageFile)
                    AspectRatio = image.width() / image.height()

                    imageW = 1000
                    imageH = 600 

                    setpixmap(image.scaled(imageW , imageH ,0,0))   
                    PosLeft = (BoxWidth  - imageW ) / 2 + 80
                    PosTop  = (BoxHeight - imageH ) / 2 +40
                    setGeometry(PosLeft,PosTop,imageW,imageH)

                }

            else
                msg = "ImageFile: -- "+ ImageFile +" -- required. Use an Image JPG of your choice"
                SendMsg(msg)
            ok

            videowidget = new qVideoWidget(win1)   
            {
                setgeometry(BoxLeft, BoxTop, BoxWidth, BoxHeight)
                setstylesheet("background-color: green")
            }

            player = new qMediaPlayer()
            {   
               setVideoOutput(videowidget)
            }

            TimerDuration = new qTimer(win1)
            {
                setinterval(1000)
                settimeoutevent("pTimeDuration()")  ### ==>> func
                start()
            }

            oFont = new qFont("",10,0,0)
            setFont( oFont)

            btnBack = new qpushbutton(win1)    {
                    setGeometry(280,20,80,20)
                    settext("Low")
                    seticon(new qicon(new qpixmap(UserIcons +"Backward.png")))
                    setclickevent( "pBackward()")
            }

            btnDur = new qpushbutton(win1)    {
                    setGeometry(360,20,140,20)
            }

            btnFwd = new qpushbutton(win1)    {
                    setGeometry(500,20,80,20)
                    settext("High")
                    seticon(new qicon(new qpixmap(UserIcons +"Forward.png")))
                    setclickevent( "pForward()")
            }

            btnVolume = new qpushbutton(win1)    {
                setGeometry(760,20,100,20)
                settext("Volume: 100")
                seticon(new qicon(new qpixmap(UserIcons +"Volume.png")))
            }

            VolumeDec = new qpushbutton(win1)
            {
                setgeometry(700,20,60,20)
                settext("Low")
                seticon(new qicon(new qpixmap(UserIcons +"VolumeLow.png")))
                setclickevent( "PVolumeDec()")
            }

            VolumeInc = new qpushbutton(win1)
            {
                setgeometry(860,20,60,20)
                settext("High")
                seticon(new qicon(new qpixmap(UserIcons +"VolumeHigh.png")))
                setclickevent( "pVolumeInc()")
            }

        show()  

    }

     exec()
}

Func pTimeDuration()
    Duration()   
return

Func Duration()

    DurPos = "Frequency: " + string(freq) + " Hz"
    btnDur.setText(DurPos)

return

Func pForward
    freq = freq + 100
    for n = 1 to 3        
         beep(freq,300)
    next
return

Func pBackward
    freq = freq - 100
    for n = 1 to 3        
         beep(freq,300)
    next
return

Func pVolumeDec()
    if volume > 0
       volume = volume - 10
       btnVolume.settext("Volume: " + volume)
       player.setVolume(volume)
    ok
return

Func pVolumeInc()
    if volume < 100
       volume = volume + 10
       btnVolume.settext("Volume: " + volume)
       player.setVolume(volume)
    ok
return

Output:

Audio frequency generator

Sidef

Translation of: Raku
var (kindS = 'sine', kind = 0, freq = 0, vol = 0, dur = 0)

while ((freq < 40) || (freq > 10000)) {
    freq = (Sys.read("Enter frequency in Hz (40 to 10000) : ", Num) || next)
}

while ((vol < 1) || (vol > 50)) {
    vol = (Sys.read("Enter volume in dB (1 to 50) : ", Num) || next)
}

while ((dur < 2) || (dur > 10)) {
    dur = (Sys.read("Enter duration in seconds (2 to 10) : ", Num) || next)
}

while ((kind < 1) || (kind > 3)) {
    kind = (Sys.read("Enter kind (1 = Sine, 2 = Square, 3 = Sawtooth) : ", Num) || next)

    given(kind) {
        when (1) { kindS = 'sine' }
        when (2) { kindS = 'square' }
        when (3) { kindS = 'sawtooth' }
    }
}

var args = ["-n", "synth", dur, kindS, freq, "vol", vol, "dB"]
Sys.run('play', args...)

SuperCollider

SuperCollider is a sound programming language, so the task is predictably easy.

// the server application detects audio hardware.
Server.default.boot;

// play a sine monotone at 440 Hz and amplitude 0.2
{ SinOsc.ar(440) * 0.2 }.play;

// use the cursor position to adjust frequency and amplitude (ranges are exponential)
{ SinOsc.ar(MouseX.kr(40, 20000, 1)) * MouseY.kr(0.001, 0.5, 1) }.play;

// use the cursor position to switch between sine wave, square wave and triangular sawtooth
// the rounding and lag smoothes the transition between them 
{ SelectX.ar(MouseX.kr(0, 2).round.lag, [ SinOsc.ar, Pulse.ar, LFTri.ar ]) * 0.1 }.play;

// the same expressed as an array of functions of a common phase
(
{
	var phase = LFSaw.ar;
	var functions = [
		{ |x| sin(x * pi) },
		{ |x| x > 0 },
		{ |x| abs(x) },
	];
	var which = MouseX.kr(0, 2);
	functions.sum { |f, i|
		abs(which - i) < 0.5 * f.(phase)
	} * 0.1
}.play
)

// sound stops on exit
Server.default.quit;

Tcl

Library: Snack

This does not work on Windows due the use of the external stty program.

package require sound

set baseFrequency 261.63
set baseAmplitude [expr {64000 / 100.0}]
set halfSemis 0
set volSteps 10

# How to adjust the generator
proc adjustPitchVolume {changePitch changeVolume} {
    global filter baseFrequency baseAmplitude halfSemis volSteps
    incr halfSemis $changePitch
    incr volSteps $changeVolume
    # Clamp the volume range
    set volSteps [expr {$volSteps < 0 ? 0 : $volSteps > 10 ? 10 : $volSteps}]
    puts -nonewline " Pitch: [expr {$halfSemis / 2.0}]  Volume: $volSteps  \r"
    set freq [expr {$baseFrequency * 2**($halfSemis/24.0)}]
    set ampl [expr {$baseAmplitude * $volSteps**2}]

    # This is where we set the actual frequency of the generated sound
    $filter configure $freq $ampl
}

# Callback handler for pressed keys
proc keyPress {} {
    global done
    switch [string tolower [read stdin 1]] {
	"q" { set done 1 }
	"u" { adjustPitchVolume 1 0 }
	"d" { adjustPitchVolume -1 0 }
	"s" { adjustPitchVolume 0 -1 }
	"l" { adjustPitchVolume 0 1 }
	default {
	    if {[eof stdin]} { set done 1 }
	}
    }
}

# Instantiate the sound generation objects from the Snack library
set filter [snack::filter generator 1 32000 0.5 sine -1]
set sound [snack::sound -rate 32050]

# Make things ready for a console application 
exec stty raw -echo <@stdin >@stdout
fconfigure stdout -buffering none
fileevent stdin readable keyPress
puts "'U' to raise pitch, 'D' to lower pitch, 'L' for louder, 'S' for softer"
puts "'Q' to quit"

# Start the playing
$sound play -filter $filter
adjustPitchVolume 0 0

# Wait until the user is finished
vwait done

# Clean up the console from its non-standard state
fileevent stdin readable {}
puts ""
exec stty -raw echo <@stdin >@stdout

# Stop the sound playing
$sound stop
exit

Wren

Translation of: Go

The ability to call external processes such as SoX is expected to be added to Wren-cli in the next release. In the meantime, we embed the following Wren script in a C host to complete this task.

/* Audio_frequency_generator.wren */

class C {
    foreign static getInput(maxSize)

    foreign static play(args)
}

var freq = 0
while (!freq || !freq.isInteger || freq < 40 || freq > 10000) {
    System.write("Enter frequency in HZ (40 to 10000) : ")
    freq = Num.fromString(C.getInput(5))
}
var freqS = freq.toString

var vol = 0
while (!vol || vol < 1 || vol > 50) {
    System.write("Enter volume in dB (1 to 50)        : ")
    vol = Num.fromString(C.getInput(2))
}
var volS = vol.toString

var dur = 0
while (!dur || dur < 2 || dur > 10) {
    System.write("Enter duration in seconds (2 to 10) : ")
    dur = Num.fromString(C.getInput(2))
}
var durS = dur.toString

var kind = 0
while (!kind || !kind.isInteger || kind < 1 || kind > 3) {
    System.write("Enter kind (1 = Sine, 2 = Square, 3 = Sawtooth) : ")
    kind = Num.fromString(C.getInput(1))
}
var kindS = "sine"
if (kind == 2) {
    kindS = "square"
} else if (kind == 3) {
    kindS = "sawtooth"
}

var args = ["-n", "-V1", "synth", durS, kindS, freqS, "vol", volS, "dB"].join(" ")
C.play(args)


We now embed this in the following C program, compile and run it.

#include <stdio.h>
#include <stdio_ext.h>
#include <stdlib.h>
#include <string.h>
#include "wren.h"

void C_getInput(WrenVM* vm) {
    int maxSize = (int)wrenGetSlotDouble(vm, 1) + 2;
    char input[maxSize];
    fgets(input, maxSize, stdin);
    __fpurge(stdin);
    input[strcspn(input, "\n")] = 0;
    wrenSetSlotString(vm, 0, (const char*)input);
}

void C_play(WrenVM* vm) {
    const char *args = wrenGetSlotString(vm, 1);
    char command[strlen(args) + 5];
    strcpy(command, "play ");
    strcat(command, args);
    system(command);
}

WrenForeignMethodFn bindForeignMethod(
    WrenVM* vm,
    const char* module,
    const char* className,
    bool isStatic,
    const char* signature) {
    if (strcmp(module, "main") == 0) {
        if (strcmp(className, "C") == 0) {
            if (isStatic && strcmp(signature, "getInput(_)") == 0) return C_getInput;
            if (isStatic && strcmp(signature, "play(_)") == 0) return C_play;
        }
    }
    return NULL;
}

static void writeFn(WrenVM* vm, const char* text) {
    printf("%s", text);
}

void errorFn(WrenVM* vm, WrenErrorType errorType, const char* module, const int line, const char* msg) {
    switch (errorType) {
        case WREN_ERROR_COMPILE:
            printf("[%s line %d] [Error] %s\n", module, line, msg);
            break;
        case WREN_ERROR_STACK_TRACE:
            printf("[%s line %d] in %s\n", module, line, msg);
            break;
        case WREN_ERROR_RUNTIME:
            printf("[Runtime Error] %s\n", msg);
            break;
    }
}

char *readFile(const char *fileName) {
    FILE *f = fopen(fileName, "r");
    fseek(f, 0, SEEK_END);
    long fsize = ftell(f);
    rewind(f);
    char *script = malloc(fsize + 1);
    fread(script, 1, fsize, f);
    fclose(f);
    script[fsize] = 0;
    return script;
}

int main(int argc, char **argv) {
    WrenConfiguration config;
    wrenInitConfiguration(&config);
    config.writeFn = &writeFn;
    config.errorFn = &errorFn;
    config.bindForeignMethodFn = &bindForeignMethod;
    WrenVM* vm = wrenNewVM(&config);
    const char* module = "main";
    const char* fileName = "Audio_frequency_generator.wren";
    char *script = readFile(fileName);
    WrenInterpretResult result = wrenInterpret(vm, module, script);
    switch (result) {
        case WREN_RESULT_COMPILE_ERROR:
            printf("Compile Error!\n");
            break;
        case WREN_RESULT_RUNTIME_ERROR:
            printf("Runtime Error!\n");
            break;
        case WREN_RESULT_SUCCESS:
            break;
    }
    wrenFreeVM(vm);
    free(script);
    return 0;
}

ZX Spectrum Basic

The ZX Spectrum is not very good at making sound. Most applications in BASIC would just produce annoying beeps, and the following is no exception. To emulate the signal generator, we just produce repetative beeps using the inbuilt speaker. The left and right keys (5 and 8) change the tone. There is no volume control on the Spectrum.

10 REM The crappest signal generator in the world
20 REM We do not check for boundary errors in this simple demo
30 LET n=1
40 LET k$=INKEY$
50 IF k$="5" THEN LET n=n-0.5
60 IF k$="8" THEN LET n=n+0.5
70 PRINT AT 0,0;n,"    "
80 BEEP 0.1,n: REM beep for 0.1 second at n semitones relative to middle C
90 GO TO 40