Yeah, I don't think my issue is the Envelope... at least not specifically.
With 16b... sure adding an Attack / Delay will remove the Pop; and adding a full ADSR Envelope makes it as smooth as butter., rather the issue ends up being 8b.
So I decided to investigate further (now I have several cups of coffee in me, and a day off).
Turns out even a Blank Signal (i.e. no Soundwave, it should be silent) still produces popping with each sample... this makes me think the issue with 8b is with the AppGameKit Audio Engine and NOT my Sampling Approach.
As such, looks like I'll always have to use 16b... and have a flag to run an algorithm to mimic 8b output.
I'll post the results when I'm happy with it.
[edit]
// Project: AudioSynthesiser
// Created: 21-03-02
SetWindowSize( 1280, 720, 0 )
SetVirtualResolution( 1280, 720 )
UseNewDefaultFonts(1)
#Constant SoundDepthBasic 8
#Constant SoundDepthStandard 16
// AGK Doesn't Support These
/*
#Constant SoundBitDepthExtended 20
#Constant SoundBitDepthEnhanced 24
*/
// Remember these are the Bitrate for 2 Channels
// Half = Mono, Double = Quad, Half * Channels = Surround
// Of course AGK Native ONLY supports Mono or Stereo
#Constant SoundBitrateAmiga 28836
#Constant SoundBitrateClassic 32000
#Constant SoundBitrateStandard 44100
#Constant SoundBitrateModern 48000
#Constant SoundChannelMono 1
#Constant SoundChannelStereo 2
// AGK Doesn't Support These
/*
#Constant SoundChannelStereo21 3
#Constant SoundChannelQuad 4
#Constant SoundChannelSurround51 6
#Constant SoundChannelSurround71 8
*/
// Generate All Note Frequencies for Octave 0 - 9
Global OctaveBase As Float = 27.500
Global OctaveRoot As Float : OctaveRoot = Pow(2.0, 1.0 / 12.0)
Global Octave As Float[9,12]
For O = 0 To 9
For N = 0 To 11
Octave[O,N] = OctaveBase * Pow( OctaveRoot, N )
Next
Octave[O,12] = 0.000
OctaveBase = OctaveBase * 2
Next
// Declare the Notation Constants
// I use d for Sharp instead of s because 'As' is a keyword; and the classic name is Diesis
#Constant C 0
#Constant Cd 1
#Constant Db 1
#Constant D 2
#Constant Dd 3
#Constant Eb 3
#Constant E 4
#Constant F 5
#Constant Fd 6
#Constant Gb 6
#Constant G 7
#Constant Gd 8
#Constant Ab 8
#Constant A 9
#Constant Ad 10
#Constant Bb 10
#Constant B 11
#Constant R 12
// R stands for Rest, i.e. no Notation
// Phew! That's much easier than writing all 10 Octaves out manually >_<
// Now when I optimise this., a Track will Pre-Define all the Notes used
// Thus only generate what is necessary and reuse existing notes
// But for now Long-Form is easier and quicker, just not as Memory Efficient
Global Note1_1 As Float = 1.0000
Global Note1_2 As Float = 0.5000
Global Note1_4 As Float = 0.2500
Global Note1_8 As Float = 0.1250
Global Note1_16 As Float = 0.06125
Global Note1_32 As Float = 0.03125
#Constant SineWave 1
#Constant SquareWave 2
#Constant SawWave 3
#Constant NoiseWave 4
#Constant AccentNormal 0
#Constant AccentStaccato 1
#Constant AccentTenuto 2
Music As Integer[]
/* Legend of Zelda - Overworld Theme */
SetTempo( 148 )
/* Bar 1 */
Music.Insert( CreateSound( Octave[5,C], Note1_2, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[5,R], Note1_8, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[5,C], Note1_8, 0.5, AccentStaccato ) )
Music.Insert( CreateSound( Octave[5,C], Note1_8, 0.5, AccentStaccato ) )
Music.Insert( CreateSound( Octave[5,C], Note1_8, 0.5, AccentStaccato ) )
/* Bar 2 */
Music.Insert( CreateSound( Octave[5,C], Note1_8, 0.5, AccentStaccato ) )
Music.Insert( CreateSound( Octave[5,R], Note1_16, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[4,Bb], Note1_8, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[5,C], Note1_4 + Note1_32, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[5,R], Note1_16 + Note1_16, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[5,C], Note1_8, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[5,C], Note1_8, 0.5, AccentStaccato ) )
/* Bar 3 */
Music.Insert( CreateSound( Octave[5,C], Note1_8, 0.5, AccentStaccato ) )
Music.Insert( CreateSound( Octave[5,R], Note1_16, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[4,Bb], Note1_8, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[5,C], Note1_4 + Note1_8, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[5,R], Note1_16, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[5,C], Note1_8, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[5,C], Note1_8, 0.5, AccentStaccato ) )
Music.Insert( CreateSound( Octave[5,C], Note1_8, 0.5, AccentStaccato ) )
Music.Insert( CreateSound( Octave[5,C], Note1_8, 0.5, AccentStaccato ) )
/* Bar 4 */
Music.Insert( CreateSound( Octave[5,C], Note1_8, 0.5, AccentStaccato ) )
Music.Insert( CreateSound( Octave[4,G], Note1_16, 0.5, AccentStaccato ) )
Music.Insert( CreateSound( Octave[4,G], Note1_16, 0.5, AccentStaccato ) )
Music.Insert( CreateSound( Octave[4,G], Note1_8, 0.5, AccentStaccato ) )
Music.Insert( CreateSound( Octave[4,G], Note1_16, 0.5, AccentStaccato ) )
Music.Insert( CreateSound( Octave[4,G], Note1_16, 0.5, AccentStaccato ) )
Music.Insert( CreateSound( Octave[4,G], Note1_8, 0.5, AccentStaccato ) )
Music.Insert( CreateSound( Octave[4,G], Note1_16, 0.5, AccentStaccato ) )
Music.Insert( CreateSound( Octave[4,G], Note1_16, 0.5, AccentStaccato ) )
Music.Insert( CreateSound( Octave[4,G], Note1_8, 0.5, AccentStaccato ) )
Music.Insert( CreateSound( Octave[4,G], Note1_8, 0.5, AccentStaccato ) )
/* Bar 5 */
Music.Insert( CreateSound( Octave[5,C], Note1_4, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[4,G], Note1_4 + Note1_8, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[5,R], Note1_16, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[5,C], Note1_16, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[5,C], Note1_16, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[5,D], Note1_16, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[5,E], Note1_16, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[5,F], Note1_16, 0.5, AccentNormal ) )
/* Bar 6 */
Music.Insert( CreateSound( Octave[5,G], Note1_2, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[5,R], Note1_8, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[5,G], Note1_16, 0.5, AccentStaccato ) )
Music.Insert( CreateSound( Octave[5,G], Note1_8, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[5,Ab], Note1_8, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[5,Bb], Note1_8, 0.5, AccentNormal ) )
/* Bar 7 */
Music.Insert( CreateSound( Octave[6,C], Note1_2 + Note1_8, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[6,C], Note1_8, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[6,C], Note1_8, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[5,Bb], Note1_8, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[5,Ab], Note1_8, 0.5, AccentNormal ) )
/* Bar 8 */
Music.Insert( CreateSound( Octave[5,Bb], Note1_8, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[5,R], Note1_8, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[5,Ab], Note1_8, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[5,G], Note1_2, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[5,G], Note1_4, 0.5, AccentNormal ) )
/* Bar 9 */
Music.Insert( CreateSound( Octave[5,F], Note1_8, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[5,F], Note1_16, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[5,G], Note1_16, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[5,Ab], Note1_2, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[5,G], Note1_8, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[5,F], Note1_8, 0.5, AccentNormal ) )
/* Bar 10 */
Music.Insert( CreateSound( Octave[5,Eb], Note1_8, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[5,Eb], Note1_16, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[5,F], Note1_16, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[5,G], Note1_2, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[5,F], Note1_8, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[5,Eb], Note1_8, 0.5, AccentNormal ) )
/* Bar 11 */
Music.Insert( CreateSound( Octave[5,D], Note1_8, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[5,D], Note1_16, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[5,E], Note1_16, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[5,Fd], Note1_2, 0.5, AccentNormal ) )
Music.Insert( CreateSound( Octave[5,A], Note1_4, 0.5, AccentNormal ) )
/* Bar 12 */
Music.Insert( CreateSound( Octave[5,G], Note1_8, 0.5, AccentStaccato ) )
Music.Insert( CreateSound( Octave[4,G], Note1_16, 0.5, AccentStaccato ) )
Music.Insert( CreateSound( Octave[4,G], Note1_16, 0.5, AccentStaccato ) )
Music.Insert( CreateSound( Octave[4,G], Note1_8, 0.5, AccentStaccato ) )
Music.Insert( CreateSound( Octave[4,G], Note1_16, 0.5, AccentStaccato ) )
Music.Insert( CreateSound( Octave[4,G], Note1_16, 0.5, AccentStaccato ) )
Music.Insert( CreateSound( Octave[4,G], Note1_8, 0.5, AccentStaccato ) )
Music.Insert( CreateSound( Octave[4,G], Note1_16, 0.5, AccentStaccato ) )
Music.Insert( CreateSound( Octave[4,G], Note1_16, 0.5, AccentStaccato ) )
Music.Insert( CreateSound( Octave[4,G], Note1_8, 0.5, AccentStaccato ) )
Music.Insert( CreateSound( Octave[4,G], Note1_8, 0.5, AccentStaccato ) )
Instance As Integer = 0
Note As Integer = 0
Intro As Integer = 0
//-- Main Loop --
Repeat
If GetSoundInstancePlaying( Instance ) <> 1
Instance = PlaySound( Music[Note] )
If Note > Music.Length - 1
Note = 32
Else
Inc Note
EndIf
EndIf
Sync()
Until GetRawKeyState( 27 )
//-- Garbage Collection --
For Note = 0 To Music.Length - 1
If GetSoundExists( Music[Note] ) Then DeleteSound( Music[Note] )
Next
End
//-- SinApprox( X )
// Quick and Dirty Sin Function, this would be faster than Sin() in C/C++
Function SinApprox( X As Float )
Local T As Float
Local O As Float
T = X * 0.15915
T = T - Floor(T)
If T < 0.5
O = (-16.0 * T * T) + (8.0 * T)
Else
O = (16.0 * T * T) - (16.0 * T) - (8.0 * T) + 8.0
EndIf
EndFunction O
//-- CreateSound( Hz, Period, Loudness, SpecialFlag )
// I'll clean this up more once I settle on specific functionality
Function CreateSound( Frequency As Float, LengthInSeconds As Float, Volume As Float, Accent As Integer )
Channels As Integer = 1
BitsPerSample As Integer = 16
SamplesPerSecond As Integer = 11025
NumberOfFrames As Integer : NumberOfFrames = (SamplesPerSecond / Channels) * LengthInSeconds
SizeInBytes As Integer : SizeInBytes = NumberOfFrames * (BitsPerSample / 8) * Channels
Buffer = CreateMemblock( 12 + SizeInBytes )
// Header
SetMemblockShort( Buffer, 0, Channels )
SetMemblockShort( Buffer, 2, BitsPerSample )
SetMemblockShort( Buffer, 4, SamplesPerSecond )
SetMemblockShort( Buffer, 8, NumberOfFrames )
FrequencyStep As Float = 0.0
FrequencyStep = 1.0 / SamplesPerSecond
Waveform As Integer = 1
fSample As Float = 0.0
Time As Float = 0.0
Amplitude As Float = 0.0
bSustained As Integer = 0
AttackTime As Float : AttackTime = LengthInSeconds * 0.01
DecayTime As Float : DecayTime = LengthInSeconds * 0.02
ReleaseTime As Float
Select Accent
Case AccentStaccato
ReleaseTime = LengthInSeconds * 0.10
EndCase
Case AccentTenuto
ReleaseTime = LengthInSeconds * 0.30
EndCase
Case Default
ReleaseTime = LengthInSeconds * 0.20
EndCase
EndSelect
Offset As Integer = 12
For I = 0 To GetMemblockSize(Buffer) - 12 Step Channels
If Frequency = 0.0
Sample = 0
Else
Select Waveform
Case SineWave
fSample = SinApprox( Frequency * 2 * 3.14159 * Time )
EndCase
Case SquareWave
If SinApprox( Frequency * 2 * 3.14159 * Time ) > 0.0
fSample = 0.992
Else
fSample = -0.992
EndIf
EndCase
Case SawWave
fSample = 0.63661 * (Frequency * 3.14159 * FMOD(Time, 1.0 / Frequency) - 1.57079)
EndCase
Case NoiseWave
fSample = SinApprox( ( Frequency * (1.0 / Random()) ) * 2 * 3.14159 * ( Time * 3.14159 ) )
EndCase
Case Default
fSample = 0.0
EndCase
EndSelect
fSample = fSample * Amplitude
// Attack - Decay - Sustain - Release
Amplitude = 0.0
If bSustained
If Time <= AttackTime
Amplitude = (Time / AttackTime) * 1.0
EndIf
If Time > AttackTime And Time <= (AttackTime + DecayTime)
Amplitude = ( (Time - AttackTime) / DecayTime ) * (Volume - 1.0) + 1.0
EndIf
If Time > (AttackTime + DecayTime)
Amplitude = Volume
EndIf
If Time > (LengthInSeconds - ReleaseTime)
bSustained = Not(bSustained)
EndIf
Else
Amplitude = ( ( Time - (LengthInSeconds - ReleaseTime) ) / ReleaseTime ) * (0.0 - Volume) + Volume
EndIf
If Amplitude <= 0.0001 Then Amplitude = 0.0
If BitsPerSample = 16
Sample = (0x7F * fSample) * 258 // Emulate 8b
Else
Sample = 0x7F * fSample
EndIf
EndIf
If BitsPerSample = 16
SetMemblockShort( Buffer, Offset, Sample * Volume )
Time = Time + FrequencyStep
Offset = Offset + 2
Else
SetMemblockByte( Buffer, Offset, Sample * Volume )
Time = Time + FrequencyStep
Offset = Offset + 1
EndIf
Next
Sound As Integer = 0
Sound = CreateSoundFromMemblock( Buffer )
DeleteMemblock( Buffer )
EndFunction Sound
//-- SetTempo( BeatsPerMinute )
Function SetTempo( BeatsPerMinute As Integer )
Local BeatsPerSecond As Float = 0.0
// Calculate Tempo in Seconds
BeatsPerSecond = (BeatsPerMinute / 60.0) / 2.0 // For some reason without Dividing by 2 the Tempo is off :\
// Now Calculate the Note Lengths & Update our Global Values
Note1_1 = BeatsPerSecond
Note1_2 = BeatsPerSecond * (1.0 / 2.0)
Note1_4 = BeatsPerSecond * (1.0 / 4.0)
Note1_8 = BeatsPerSecond * (1.0 / 8.0)
Note1_16 = BeatsPerSecond * (1.0 / 16.0)
Note1_32 = BeatsPerSecond * (1.0 / 32.0)
EndFunction
Alright... I think it's in a "Happy" place for now., need to comment the actual CreateSound Function better... and there are a few functionality elements I want to improve.
Still, what I've got is a rather nice base for creating Chiptunes / Emulating "Old School" 8bit System Audio.
The Noise Function needs a bit of work to be used for Drums or such., but still it's a start.
Also replaced the Music, which required adding a "Tempo" Function along with changes to how the Notes are Formed (as it uses Accents to get the sound right)...
Have a play with it... what I want to do is create a Tool for actually writing Music Tracks and a Tracker / Midi Style Format; so the Music is generated "OnLoad"
I don't think AppGameKit is fast enough to do it "Real-Time" but maybe... have to see what I can work out.